activerecord-reputation-system 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -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