activerecord-reputation-system 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.
- 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
|