activerecord-reputation-system 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +177 -0
- data/README.md +255 -0
- data/Rakefile +20 -0
- data/lib/generators/reputation_system/reputation_system_generator.rb +35 -0
- data/lib/generators/reputation_system/templates/create_reputation_system.rb +59 -0
- data/lib/models/rs_evaluation.rb +52 -0
- data/lib/models/rs_reputation.rb +181 -0
- data/lib/models/rs_reputation_message.rb +46 -0
- data/lib/reputation_system.rb +28 -0
- data/lib/reputation_system/base.rb +70 -0
- data/lib/reputation_system/evaluation.rb +88 -0
- data/lib/reputation_system/network.rb +222 -0
- data/lib/reputation_system/normalization.rb +50 -0
- data/lib/reputation_system/query.rb +102 -0
- data/lib/reputation_system/reputation.rb +39 -0
- data/lib/reputation_system/scope.rb +37 -0
- data/lib/reputation_system/version.rb +19 -0
- data/spec/models/rs_evaluation_spec.rb +44 -0
- data/spec/models/rs_reputation_message_spec.rb +47 -0
- data/spec/models/rs_reputation_spec.rb +101 -0
- data/spec/reputation_system/base_spec.rb +68 -0
- data/spec/reputation_system/evaluation_spec.rb +289 -0
- data/spec/reputation_system/network_spec.rb +27 -0
- data/spec/reputation_system/normalization_spec.rb +68 -0
- data/spec/reputation_system/query_spec.rb +183 -0
- data/spec/reputation_system/reputation_spec.rb +119 -0
- data/spec/reputation_system/scope_spec.rb +43 -0
- data/spec/spec_helper.rb +190 -0
- metadata +168 -0
@@ -0,0 +1,27 @@
|
|
1
|
+
##
|
2
|
+
# Copyright 2012 Twitter, Inc
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
##
|
16
|
+
|
17
|
+
require 'spec_helper'
|
18
|
+
|
19
|
+
describe ActiveRecord::Base do
|
20
|
+
|
21
|
+
before(:each) do
|
22
|
+
@user = User.create!(:name => 'jack')
|
23
|
+
@question = Question.create!(:text => 'Does this work?', :author_id => @user.id)
|
24
|
+
@answer = Answer.create!(:text => 'Yes!', :author_id => @user.id, :question_id => @question.id)
|
25
|
+
@phrase = Phrase.create!(:text => "One")
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
##
|
2
|
+
# Copyright 2012 Twitter, Inc
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
##
|
16
|
+
|
17
|
+
require 'spec_helper'
|
18
|
+
|
19
|
+
describe ActiveRecord::Base do
|
20
|
+
|
21
|
+
before(:each) do
|
22
|
+
@user = User.create!(:name => 'jack')
|
23
|
+
@question = Question.create!(:text => 'Does this work?', :author_id => @user.id)
|
24
|
+
@answer = Answer.create!(:text => 'Yes!', :author_id => @user.id, :question_id => @question.id)
|
25
|
+
@phrase = Phrase.create!(:text => "One")
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "#normalized_reputation_value_for" do
|
29
|
+
it "should return 0 as if there is no data" do
|
30
|
+
@question.normalized_reputation_value_for(:total_votes).should == 0
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should return appropriate value in case of valid input" do
|
34
|
+
question2 = Question.create!(:text => 'Does this work too?', :author_id => @user.id)
|
35
|
+
question3 = Question.create!(:text => 'Does this work too?', :author_id => @user.id)
|
36
|
+
@question.add_evaluation(:total_votes, 1, @user)
|
37
|
+
question2.add_evaluation(:total_votes, 2, @user)
|
38
|
+
question3.add_evaluation(:total_votes, 3, @user)
|
39
|
+
@question.normalized_reputation_value_for(:total_votes).should == 0
|
40
|
+
question2.normalized_reputation_value_for(:total_votes).should == 0.5
|
41
|
+
question3.normalized_reputation_value_for(:total_votes).should == 1
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should raise exception if invalid reputation name is given" do
|
45
|
+
lambda {@question.normalized_reputation_value_for(:invalid)}.should raise_error(ArgumentError)
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should raise exception if scope is given for reputation with no scopes" do
|
49
|
+
lambda {@question.normalized_reputation_value_for(:difficulty, :s1)}.should raise_error(ArgumentError)
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should raise exception if scope is not given for reputation with scopes" do
|
53
|
+
lambda {@phrase.normalized_reputation_value_for(:difficulty_with_scope)}.should raise_error(ArgumentError)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe "#exclude_all_reputations_for_normalization" do
|
58
|
+
it "should activate all reputation" do
|
59
|
+
@question2 = Question.create!(:text => 'Does this work??', :author_id => @user.id)
|
60
|
+
@question2.add_evaluation(:total_votes, 70, @user)
|
61
|
+
@question.add_evaluation(:total_votes, 100, @user)
|
62
|
+
@question.deactivate_all_reputations
|
63
|
+
RSReputation.maximum(:value, :conditions => {:reputation_name => 'total_votes', :active => true}).should == 70
|
64
|
+
@question.activate_all_reputations
|
65
|
+
RSReputation.maximum(:value, :conditions => {:reputation_name => 'total_votes', :active => true}).should == 100
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,183 @@
|
|
1
|
+
##
|
2
|
+
# Copyright 2012 Twitter, Inc
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
##
|
16
|
+
|
17
|
+
require 'spec_helper'
|
18
|
+
|
19
|
+
describe ActiveRecord::Base do
|
20
|
+
|
21
|
+
before(:each) do
|
22
|
+
@user = User.create!(:name => 'jack')
|
23
|
+
@question = Question.create!(:text => 'Does this work?', :author_id => @user.id)
|
24
|
+
@answer = Answer.create!(:text => 'Yes!', :author_id => @user.id, :question_id => @question.id)
|
25
|
+
@phrase = Phrase.create!(:text => "One")
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "#find_with_reputation" do
|
29
|
+
context "Without Scopes" do
|
30
|
+
before :each do
|
31
|
+
@question.add_evaluation(:total_votes, 3, @user)
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should return result with given reputation" do
|
35
|
+
res = Question.find_with_reputation(:total_votes, :all, {})
|
36
|
+
res.should == [@question]
|
37
|
+
res[0].total_votes.should_not be_nil
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should retain select option" do
|
41
|
+
res = Question.find_with_reputation(:total_votes, :all, {:select => "questions.id"})
|
42
|
+
res.should == [@question]
|
43
|
+
res[0].id.should_not be_nil
|
44
|
+
lambda {res[0].text}.should raise_error
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should retain conditions option" do
|
48
|
+
@question2 = Question.create!(:text => 'Does this work?', :author_id => @user.id)
|
49
|
+
@question2.add_evaluation(:total_votes, 5, @user)
|
50
|
+
res = Question.find_with_reputation(:total_votes, :all, {:conditions => "total_votes > 4"})
|
51
|
+
res.should == [@question2]
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should retain joins option" do
|
55
|
+
res = Question.find_with_reputation(:total_votes, :all, {
|
56
|
+
:select => "questions.*, users.name AS user_name",
|
57
|
+
:joins => "JOIN users ON questions.author_id = users.id"})
|
58
|
+
res.should == [@question]
|
59
|
+
res[0].user_name.should == @user.name
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
context "With Scopes" do
|
64
|
+
before :each do
|
65
|
+
@trans_ja = Translation.create!(:text => "Ichi", :user => @user, :locale => "ja", :phrase => @phrase)
|
66
|
+
@trans_ja.add_evaluation(:votes, 3, @user)
|
67
|
+
@trans_fr = Translation.create!(:text => "Ichi", :user => @user, :locale => "fr", :phrase => @phrase)
|
68
|
+
@trans_fr.add_evaluation(:votes, 6, @user)
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should return result with given reputation" do
|
72
|
+
res = Phrase.find_with_reputation(:maturity, :ja, :all, {})
|
73
|
+
res.should == [@phrase]
|
74
|
+
res[0].maturity.should == 3
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
describe "#count_with_reputation" do
|
80
|
+
context "Without Scopes" do
|
81
|
+
before :each do
|
82
|
+
@question.add_evaluation(:total_votes, 3, @user)
|
83
|
+
end
|
84
|
+
|
85
|
+
it "should return result with given reputation" do
|
86
|
+
Question.count_with_reputation(:total_votes, :all, {
|
87
|
+
:conditions => "total_votes < 2"
|
88
|
+
}).should == 0
|
89
|
+
Question.count_with_reputation(:total_votes, :all, {
|
90
|
+
:conditions => "total_votes > 2"
|
91
|
+
}).should == 1
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
context "With Scopes" do
|
96
|
+
before :each do
|
97
|
+
@trans_ja = Translation.create!(:text => "Ichi", :user => @user, :locale => "ja", :phrase => @phrase)
|
98
|
+
@trans_ja.add_evaluation(:votes, 3, @user)
|
99
|
+
@trans_fr = Translation.create!(:text => "Ichi", :user => @user, :locale => "fr", :phrase => @phrase)
|
100
|
+
@trans_fr.add_evaluation(:votes, 6, @user)
|
101
|
+
end
|
102
|
+
|
103
|
+
it "should return result with given reputation" do
|
104
|
+
Phrase.count_with_reputation(:maturity, :ja, :all, {
|
105
|
+
:conditions => "maturity < 2"
|
106
|
+
}).should == 0
|
107
|
+
Phrase.count_with_reputation(:maturity, :ja, :all, {
|
108
|
+
:conditions => "maturity > 2"
|
109
|
+
}).should == 1
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
describe "#find_with_normalized_reputation" do
|
115
|
+
context "Without Scopes" do
|
116
|
+
before :each do
|
117
|
+
@question.add_evaluation(:total_votes, 3, @user)
|
118
|
+
end
|
119
|
+
|
120
|
+
it "should return result with given normalized reputation" do
|
121
|
+
@question2 = Question.create!(:text => 'Does this work?', :author_id => @user.id)
|
122
|
+
@question2.add_evaluation(:total_votes, 6, @user)
|
123
|
+
res = Question.find_with_normalized_reputation(:total_votes, :all, {})
|
124
|
+
res.should == [@question, @question2]
|
125
|
+
res[0].normalized_total_votes.should be_within(DELTA).of(0)
|
126
|
+
res[1].normalized_total_votes.should be_within(DELTA).of(1)
|
127
|
+
end
|
128
|
+
|
129
|
+
it "should retain select option" do
|
130
|
+
res = Question.find_with_normalized_reputation(:total_votes, :all, {:select => "questions.id"})
|
131
|
+
res.should == [@question]
|
132
|
+
res[0].id.should_not be_nil
|
133
|
+
lambda {res[0].text}.should raise_error
|
134
|
+
end
|
135
|
+
|
136
|
+
it "should retain conditions option" do
|
137
|
+
@question2 = Question.create!(:text => 'Does this work?', :author_id => @user.id)
|
138
|
+
@question2.add_evaluation(:total_votes, 6, @user)
|
139
|
+
res = Question.find_with_normalized_reputation(:total_votes, :all, {:conditions => "normalized_total_votes > 0.6"})
|
140
|
+
res.should == [@question2]
|
141
|
+
end
|
142
|
+
|
143
|
+
it "should retain joins option" do
|
144
|
+
res = Question.find_with_normalized_reputation(:total_votes, :all, {
|
145
|
+
:select => "questions.*, users.name AS user_name",
|
146
|
+
:joins => "JOIN users ON questions.author_id = users.id"})
|
147
|
+
res.should == [@question]
|
148
|
+
res[0].user_name.should == @user.name
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
context "With Scopes" do
|
153
|
+
before :each do
|
154
|
+
@trans_ja = Translation.create!(:text => "Ichi", :user => @user, :locale => "ja", :phrase => @phrase)
|
155
|
+
@trans_ja.add_evaluation(:votes, 3, @user)
|
156
|
+
@trans_fr = Translation.create!(:text => "Ichi", :user => @user, :locale => "fr", :phrase => @phrase)
|
157
|
+
@trans_fr.add_evaluation(:votes, 6, @user)
|
158
|
+
end
|
159
|
+
|
160
|
+
it "should return result with given reputation" do
|
161
|
+
res = Phrase.find_with_normalized_reputation(:maturity, :ja, :all, {})
|
162
|
+
res.should == [@phrase]
|
163
|
+
res[0].normalized_maturity.should be_within(DELTA).of(0)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
describe "#find_with_reputation_sql" do
|
169
|
+
it "should return a corresponding sql statement" do
|
170
|
+
sql = Question.find_with_reputation_sql(:total_votes, :all, {
|
171
|
+
:select => "questions.*, users.name AS user_name",
|
172
|
+
:joins => "JOIN users ON questions.author_id = users.id",
|
173
|
+
:conditions => "total_votes > 0.6",
|
174
|
+
:order => "total_votes"})
|
175
|
+
sql.should ==
|
176
|
+
"SELECT questions.*, users.name AS user_name, COALESCE(rs_reputations.value, 0) AS total_votes "\
|
177
|
+
"FROM \"questions\" JOIN users ON questions.author_id = users.id "\
|
178
|
+
"LEFT JOIN rs_reputations ON questions.id = rs_reputations.target_id AND rs_reputations.target_type = 'Question' AND rs_reputations.reputation_name = 'total_votes' AND rs_reputations.active = 't' "\
|
179
|
+
"WHERE (total_votes > 0.6) "\
|
180
|
+
"ORDER BY total_votes"
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
##
|
2
|
+
# Copyright 2012 Twitter, Inc
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
##
|
16
|
+
|
17
|
+
require 'spec_helper'
|
18
|
+
|
19
|
+
describe ActiveRecord::Base do
|
20
|
+
|
21
|
+
before(:each) do
|
22
|
+
@user = User.create!(:name => 'jack')
|
23
|
+
@question = Question.create!(:text => 'Does this work?', :author_id => @user.id)
|
24
|
+
@answer = Answer.create!(:text => 'Yes!', :author_id => @user.id, :question_id => @question.id)
|
25
|
+
@phrase = Phrase.create!(:text => "One")
|
26
|
+
end
|
27
|
+
|
28
|
+
context "Primary Reputation" do
|
29
|
+
describe "#reputation_value_for" do
|
30
|
+
it "should return 0 as a default" do
|
31
|
+
@question.reputation_value_for(:total_votes).should == 0
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should return appropriate value in case of valid input" do
|
35
|
+
user2 = User.new(:name => 'dick')
|
36
|
+
@question.add_evaluation(:total_votes, 1, @user)
|
37
|
+
@question.add_evaluation(:total_votes, 1, user2)
|
38
|
+
@question.reputation_value_for(:total_votes).should == 2
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should raise exception if invalid reputation name is given" do
|
42
|
+
lambda {@question.reputation_value_for(:invalid)}.should raise_error(ArgumentError)
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should raise exception if scope is given for reputation with no scopes" do
|
46
|
+
lambda {@question.reputation_value_for(:difficulty, :s1)}.should raise_error(ArgumentError)
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should raise exception if scope is not given for reputation with scopes" do
|
50
|
+
lambda {@phrase.reputation_value_for(:difficulty_with_scope)}.should raise_error(ArgumentError)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe "#rank_for" do
|
55
|
+
context "without scope" do
|
56
|
+
before :each do
|
57
|
+
@question.add_evaluation(:total_votes, 3, @user)
|
58
|
+
@question2 = Question.create!(:text => 'Does this work?', :author_id => @user.id)
|
59
|
+
@question2.add_evaluation(:total_votes, 5, @user)
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should return rank properly" do
|
63
|
+
@question.rank_for(:total_votes).should == 2
|
64
|
+
@question2.rank_for(:total_votes).should == 1
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
context "with scope" do
|
69
|
+
before :each do
|
70
|
+
@trans_ja = Translation.create!(:text => "Ichi", :user => @user, :locale => "ja", :phrase => @phrase)
|
71
|
+
@trans_ja.add_evaluation(:votes, 3, @user)
|
72
|
+
@phrase2 = Phrase.create!(:text => "One")
|
73
|
+
@trans_fr = Translation.create!(:text => "Ichi", :user => @user, :locale => "ja", :phrase => @phrase2)
|
74
|
+
@trans_fr.add_evaluation(:votes, 6, @user)
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should return rank properly" do
|
78
|
+
@phrase.rank_for(:maturity, :ja).should == 2
|
79
|
+
@phrase2.rank_for(:maturity, :ja).should == 1
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
context "Non-Primary Reputation with Gathering Aggregation" do
|
86
|
+
describe "#reputation_value_for" do
|
87
|
+
it "should always have correct updated value" do
|
88
|
+
question2 = Question.create!(:text => 'Does this work?', :author_id => @user.id)
|
89
|
+
@user.reputation_value_for(:question_karma).should == 0
|
90
|
+
@question.add_evaluation(:total_votes, 1, @user)
|
91
|
+
@user.reputation_value_for(:question_karma).should == 1
|
92
|
+
question2.add_evaluation(:total_votes, 1, @user)
|
93
|
+
@user.reputation_value_for(:question_karma).should == 2
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
context "Non-Primary Reputation with Mixing Aggregation" do
|
99
|
+
describe "#reputation_value_for" do
|
100
|
+
it "should always have correct updated value" do
|
101
|
+
question = Question.create!(:text => 'Does this work?', :author_id => @user.id)
|
102
|
+
question2 = Question.create!(:text => 'Does this work?', :author_id => @user.id)
|
103
|
+
question.add_evaluation(:difficulty, 1, @user)
|
104
|
+
question2.add_evaluation(:difficulty, 2, @user)
|
105
|
+
question.add_evaluation(:total_votes, 1, @user)
|
106
|
+
question2.add_evaluation(:total_votes, 1, @user)
|
107
|
+
answer = Answer.create!(:text => 'Yes!', :author_id => @user.id, :question_id => question.id)
|
108
|
+
answer2 = Answer.create!(:text => 'Yes!', :author_id => @user.id, :question_id => question2.id)
|
109
|
+
answer.add_evaluation(:avg_rating, 3, @user)
|
110
|
+
answer2.add_evaluation(:avg_rating, 2, @user)
|
111
|
+
answer.reputation_value_for(:weighted_avg_rating).should == 3
|
112
|
+
answer2.reputation_value_for(:weighted_avg_rating).should == 4
|
113
|
+
@user.reputation_value_for(:answer_karma).should be_within(DELTA).of(3.5)
|
114
|
+
@user.reputation_value_for(:question_karma).should be_within(DELTA).of(2)
|
115
|
+
@user.reputation_value_for(:karma).should be_within(DELTA).of(1.4)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
##
|
2
|
+
# Copyright 2012 Twitter, Inc
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
##
|
16
|
+
|
17
|
+
require 'spec_helper'
|
18
|
+
|
19
|
+
describe ActiveRecord::Base do
|
20
|
+
|
21
|
+
before(:each) do
|
22
|
+
@user = User.create!(:name => 'jack')
|
23
|
+
@question = Question.create!(:text => 'Does this work?', :author_id => @user.id)
|
24
|
+
@answer = Answer.create!(:text => 'Yes!', :author_id => @user.id, :question_id => @question.id)
|
25
|
+
@phrase = Phrase.create!(:text => "One")
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "#add_scope_for" do
|
29
|
+
it "should add scope if the reputation has scopes defined" do
|
30
|
+
Phrase.add_scope_for(:difficulty_with_scope, :s4)
|
31
|
+
@phrase.add_evaluation(:difficulty_with_scope, 2, @user, :s4)
|
32
|
+
@phrase.reputation_value_for(:difficulty_with_scope, :s4).should == 2
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should raise exception if the scope already exist" do
|
36
|
+
lambda{Phrase.add_scope_for(:difficulty_with_scope, :s1)}.should raise_error(ArgumentError)
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should raise exception if the reputation does not have scopes defined" do
|
40
|
+
lambda{Question.add_scope_for(:difficulty, :s1)}.should raise_error(ArgumentError)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,190 @@
|
|
1
|
+
##
|
2
|
+
# Copyright 2012 Twitter, Inc
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
##
|
16
|
+
|
17
|
+
require 'active_record'
|
18
|
+
require 'database_cleaner'
|
19
|
+
require 'sqlite3'
|
20
|
+
require 'reputation_system'
|
21
|
+
|
22
|
+
DELTA = 0.000001
|
23
|
+
|
24
|
+
ActiveRecord::Base.establish_connection(
|
25
|
+
:adapter => "sqlite3",
|
26
|
+
:database => ":memory:"
|
27
|
+
)
|
28
|
+
|
29
|
+
RSpec.configure do |config|
|
30
|
+
config.before(:each) do
|
31
|
+
DatabaseCleaner.start
|
32
|
+
end
|
33
|
+
config.after(:each) do
|
34
|
+
DatabaseCleaner.clean
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
ActiveRecord::Migration.verbose = false
|
39
|
+
|
40
|
+
ActiveRecord::Schema.define do
|
41
|
+
create_table :rs_evaluations do |t|
|
42
|
+
t.string :reputation_name
|
43
|
+
t.references :source, :polymorphic => true
|
44
|
+
t.references :target, :polymorphic => true
|
45
|
+
t.float :value, :default => 0
|
46
|
+
t.timestamps
|
47
|
+
end
|
48
|
+
|
49
|
+
add_index :rs_evaluations, :reputation_name
|
50
|
+
add_index :rs_evaluations, [:target_id, :target_type]
|
51
|
+
add_index :rs_evaluations, [:source_id, :source_type]
|
52
|
+
|
53
|
+
create_table :rs_reputations do |t|
|
54
|
+
t.string :reputation_name
|
55
|
+
t.float :value, :default => 0
|
56
|
+
t.string :aggregated_by
|
57
|
+
t.references :target, :polymorphic => true
|
58
|
+
t.boolean :active, :default => true
|
59
|
+
t.timestamps
|
60
|
+
end
|
61
|
+
|
62
|
+
add_index :rs_reputations, :reputation_name
|
63
|
+
add_index :rs_reputations, [:target_id, :target_type]
|
64
|
+
|
65
|
+
create_table :rs_reputation_messages do |t|
|
66
|
+
t.references :sender, :polymorphic => true
|
67
|
+
t.integer :receiver_id
|
68
|
+
t.float :weight, :default => 1
|
69
|
+
t.timestamps
|
70
|
+
end
|
71
|
+
|
72
|
+
add_index :rs_reputation_messages, [:sender_id, :sender_type]
|
73
|
+
add_index :rs_reputation_messages, :receiver_id
|
74
|
+
|
75
|
+
create_table :users do |t|
|
76
|
+
t.string :name
|
77
|
+
t.timestamps
|
78
|
+
end
|
79
|
+
|
80
|
+
create_table :answers do |t|
|
81
|
+
t.integer :author_id
|
82
|
+
t.integer :question_id
|
83
|
+
t.string :text
|
84
|
+
t.timestamps
|
85
|
+
end
|
86
|
+
|
87
|
+
create_table :questions do |t|
|
88
|
+
t.integer :author_id
|
89
|
+
t.string :text
|
90
|
+
t.timestamps
|
91
|
+
end
|
92
|
+
|
93
|
+
create_table :phrases do |t|
|
94
|
+
t.string :text
|
95
|
+
t.timestamps
|
96
|
+
end
|
97
|
+
|
98
|
+
create_table :translations do |t|
|
99
|
+
t.integer :user_id
|
100
|
+
t.integer :phrase_id
|
101
|
+
t.string :text
|
102
|
+
t.string :locale
|
103
|
+
t.timestamps
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
class User < ActiveRecord::Base
|
108
|
+
has_many :answers, :foreign_key => 'author_id', :class_name => 'Answer'
|
109
|
+
has_many :questions, :foreign_key => 'author_id', :class_name => 'Question'
|
110
|
+
|
111
|
+
has_reputation :karma,
|
112
|
+
:source => [
|
113
|
+
{:reputation => :question_karma},
|
114
|
+
{:reputation => :answer_karma, :weight => 0.2}],
|
115
|
+
:aggregated_by => :product
|
116
|
+
|
117
|
+
has_reputation :question_karma,
|
118
|
+
:source => {:reputation => :total_votes, :of => :questions},
|
119
|
+
:aggregated_by => :sum
|
120
|
+
|
121
|
+
has_reputation :answer_karma,
|
122
|
+
:source => {:reputation => :weighted_avg_rating, :of => :answers},
|
123
|
+
:aggregated_by => :average
|
124
|
+
end
|
125
|
+
|
126
|
+
class Question < ActiveRecord::Base
|
127
|
+
belongs_to :author, :class_name => 'User'
|
128
|
+
has_many :answers
|
129
|
+
|
130
|
+
has_reputation :total_votes,
|
131
|
+
:source => :user,
|
132
|
+
:aggregated_by => :sum,
|
133
|
+
:source_of => {:reputation => :question_karma, :of => :author}
|
134
|
+
|
135
|
+
has_reputation :difficulty,
|
136
|
+
:source => :user,
|
137
|
+
:aggregated_by => :average
|
138
|
+
end
|
139
|
+
|
140
|
+
class Answer < ActiveRecord::Base
|
141
|
+
belongs_to :author, :class_name => 'User'
|
142
|
+
belongs_to :question
|
143
|
+
|
144
|
+
has_reputation :weighted_avg_rating,
|
145
|
+
:source => [
|
146
|
+
{:reputation => :avg_rating},
|
147
|
+
{:reputation => :difficulty, :of => :question}],
|
148
|
+
:aggregated_by => :product,
|
149
|
+
:source_of => {:reputation => :answer_karma, :of => :author}
|
150
|
+
|
151
|
+
has_reputation :avg_rating,
|
152
|
+
:source => :user,
|
153
|
+
:aggregated_by => :average,
|
154
|
+
:init_value => 1
|
155
|
+
end
|
156
|
+
|
157
|
+
class Phrase < ActiveRecord::Base
|
158
|
+
has_many :translations do
|
159
|
+
def for(locale)
|
160
|
+
self.find_all_by_locale(locale.to_s)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
has_reputation :maturity_all,
|
165
|
+
:source => [
|
166
|
+
{:reputation => :maturity, :of => :self, :scope => :ja},
|
167
|
+
{:reputation => :maturity, :of => :self, :scope => :fr}],
|
168
|
+
:aggregated_by => :sum
|
169
|
+
|
170
|
+
has_reputation :maturity,
|
171
|
+
:source => { :reputation => :votes, :of => lambda {|this, s| this.translations.for(s)} },
|
172
|
+
:aggregated_by => :sum,
|
173
|
+
:scopes => [:ja, :fr, :de],
|
174
|
+
:source_of => {:reputation => :maturity_all, :of => :self, :defined_for_scope => [:ja, :fr]}
|
175
|
+
|
176
|
+
has_reputation :difficulty_with_scope,
|
177
|
+
:source => :user,
|
178
|
+
:aggregated_by => :average,
|
179
|
+
:scopes => [:s1, :s2, :s3]
|
180
|
+
end
|
181
|
+
|
182
|
+
class Translation < ActiveRecord::Base
|
183
|
+
belongs_to :user
|
184
|
+
belongs_to :phrase
|
185
|
+
|
186
|
+
has_reputation :votes,
|
187
|
+
:source => :user,
|
188
|
+
:aggregated_by => :sum,
|
189
|
+
:source_of => {:reputation => :maturity, :of => :phrase, :scope => :locale}
|
190
|
+
end
|