activerecord-reputation-system 1.1.0 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +3 -1
- data/lib/generators/reputation_system/reputation_system_generator.rb +13 -2
- data/lib/generators/reputation_system/templates/add_evaluations_index.rb +25 -0
- data/lib/generators/reputation_system/templates/add_reputation_messages_index.rb +25 -0
- data/lib/generators/reputation_system/templates/add_reputations_index.rb +25 -0
- data/lib/reputation_system/network.rb +10 -15
- data/lib/reputation_system/version.rb +1 -1
- data/spec/models/rs_evaluation_spec.rb +1 -1
- data/spec/models/rs_reputation_message_spec.rb +4 -4
- data/spec/models/rs_reputation_spec.rb +6 -6
- data/spec/reputation_system/base_spec.rb +4 -4
- data/spec/reputation_system/evaluation_spec.rb +5 -5
- metadata +6 -3
data/README.md
CHANGED
@@ -20,10 +20,12 @@ Run:
|
|
20
20
|
|
21
21
|
```ruby
|
22
22
|
bundle install
|
23
|
-
rails
|
23
|
+
rails generate reputation_system
|
24
24
|
rake db:migrate
|
25
25
|
```
|
26
26
|
|
27
|
+
* Please do the installation on every upgrade as it may include new migration files.
|
28
|
+
|
27
29
|
## Usage Example
|
28
30
|
|
29
31
|
Let's say we want to keep track of user karma in Q&A site where user karma is sum of questioning skill and answering skill. Questioning skill is sum of votes for user's questions and Answering skill is sum of average rating of user's answers. This can be defined as follow:
|
@@ -30,6 +30,17 @@ class ReputationSystemGenerator < Rails::Generators::Base
|
|
30
30
|
end
|
31
31
|
|
32
32
|
def create_migration_files
|
33
|
-
|
33
|
+
create_migration_file_if_not_exist 'create_reputation_system'
|
34
|
+
create_migration_file_if_not_exist 'add_reputations_index'
|
35
|
+
create_migration_file_if_not_exist 'add_evaluations_index'
|
36
|
+
create_migration_file_if_not_exist 'add_reputation_messages_index'
|
34
37
|
end
|
35
|
-
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def create_migration_file_if_not_exist(file_name)
|
42
|
+
unless self.class.migration_exists?(File.dirname(File.expand_path("db/migrate/#{file_name}")), file_name)
|
43
|
+
migration_template "#{file_name}.rb", "db/migrate/#{file_name}.rb"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,25 @@
|
|
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
|
+
class AddEvaluationsIndex < ActiveRecord::Migration
|
18
|
+
def self.up
|
19
|
+
add_index :rs_evaluations, [:reputation_name, :source_id, :source_type, :target_id, :target_type], :name => "index_rs_evaluations_on_reputation_name_and_source_and_target"
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.down
|
23
|
+
remove_index :rs_evaluations, :column => [:reputation_name, :source_id, :source_type, :target_id, :target_type]
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,25 @@
|
|
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
|
+
class AddReputationMessagesIndex < ActiveRecord::Migration
|
18
|
+
def self.up
|
19
|
+
add_index :rs_reputation_messages, [:receiver_id, :sender_id, :sender_type], :name => "index_rs_reputation_messages_on_receiver_id_and_sender"
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.down
|
23
|
+
remove_index :rs_reputation_messages, :column => [:receiver_id, :sender_id, :sender_type]
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,25 @@
|
|
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
|
+
class AddReputationsIndex < ActiveRecord::Migration
|
18
|
+
def self.up
|
19
|
+
add_index :rs_reputations, [:reputation_name, :target_id, :target_type], :name => "index_rs_reputations_on_reputation_name_and_target"
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.down
|
23
|
+
remove_index :rs_reputations, :column => [:reputation_name, :target_id, :target_type]
|
24
|
+
end
|
25
|
+
end
|
@@ -23,20 +23,18 @@ module ReputationSystem
|
|
23
23
|
end
|
24
24
|
|
25
25
|
def get_reputation_defs(class_name)
|
26
|
-
network[class_name.to_sym]
|
27
|
-
network[class_name.to_sym]
|
26
|
+
network[class_name.to_sym] ||= {}
|
28
27
|
end
|
29
28
|
|
30
29
|
def get_reputation_def(class_name, reputation_name)
|
31
30
|
reputation_defs = get_reputation_defs(class_name)
|
32
|
-
reputation_defs[reputation_name.to_sym]
|
33
|
-
reputation_defs[reputation_name.to_sym]
|
31
|
+
reputation_defs[reputation_name.to_sym] ||= {}
|
34
32
|
end
|
35
33
|
|
36
34
|
def add_reputation_def(class_name, reputation_name, options)
|
37
35
|
reputation_defs = get_reputation_defs(class_name)
|
38
36
|
options[:source] = convert_to_array_if_hash(options[:source])
|
39
|
-
options[:source_of]
|
37
|
+
options[:source_of] ||= []
|
40
38
|
options[:source_of] = convert_to_array_if_hash(options[:source_of])
|
41
39
|
assign_self_as_default_value_for_of_attr(options[:source])
|
42
40
|
assign_self_as_default_value_for_of_attr(options[:source_of])
|
@@ -67,7 +65,7 @@ module ReputationSystem
|
|
67
65
|
def add_scope_for(class_name, reputation_name, scope)
|
68
66
|
options = get_reputation_def(class_name, reputation_name)
|
69
67
|
if has_scope?(class_name, reputation_name, scope)
|
70
|
-
raise ArgumentError, "#{scope
|
68
|
+
raise ArgumentError, "#{scope} is already defined for #{reputation_name}"
|
71
69
|
else
|
72
70
|
options[:scopes].push scope.to_sym if options[:scopes]
|
73
71
|
create_scoped_reputation_def(class_name, reputation_name, scope, options)
|
@@ -87,7 +85,7 @@ module ReputationSystem
|
|
87
85
|
scope = scope.to_sym if scope
|
88
86
|
validate_scope_necessity(class_name, reputation_name, scope)
|
89
87
|
validate_scope_existence(class_name, reputation_name, scope)
|
90
|
-
"#{reputation_name
|
88
|
+
"#{reputation_name}#{"_#{scope}" if scope}"
|
91
89
|
end
|
92
90
|
|
93
91
|
def get_weight_of_source_from_reputation_name_of_target(target, source_name, reputation_name)
|
@@ -106,17 +104,15 @@ module ReputationSystem
|
|
106
104
|
protected
|
107
105
|
|
108
106
|
def network
|
109
|
-
@network
|
110
|
-
@network
|
107
|
+
@network ||= {}
|
111
108
|
end
|
112
109
|
|
113
110
|
def data_for_derive_later
|
114
|
-
@data_for_derive_later
|
115
|
-
@data_for_derive_later
|
111
|
+
@data_for_derive_later ||= {}
|
116
112
|
end
|
117
113
|
|
118
114
|
def create_scoped_reputation_def(class_name, reputation_name, scope, options)
|
119
|
-
raise ArgumentError, "#{reputation_name
|
115
|
+
raise ArgumentError, "#{reputation_name} does not have scope." unless has_scopes?(class_name, reputation_name)
|
120
116
|
scope_options = {}
|
121
117
|
reputation_def = get_reputation_def(class_name, reputation_name)
|
122
118
|
if is_primary_reputation?(class_name, reputation_name)
|
@@ -198,12 +194,11 @@ module ReputationSystem
|
|
198
194
|
end
|
199
195
|
|
200
196
|
def convert_to_array_if_hash(tar)
|
201
|
-
tar
|
202
|
-
tar
|
197
|
+
tar.is_a?(Hash) ? [tar] : tar
|
203
198
|
end
|
204
199
|
|
205
200
|
def assign_self_as_default_value_for_of_attr(tar)
|
206
|
-
tar
|
201
|
+
tar.each { |s| s[:of] = :self unless s[:of] } if tar.is_a? Array
|
207
202
|
end
|
208
203
|
|
209
204
|
def validate_scope_necessity(class_name, reputation_name, scope)
|
@@ -26,7 +26,7 @@ describe RSEvaluation do
|
|
26
26
|
before :each do
|
27
27
|
@attributes = {:reputation_name => 'total_votes', :source => @user, :target => @question, :value => 1}
|
28
28
|
end
|
29
|
-
it "should not be able to create an evaluation from given source if it has
|
29
|
+
it "should not be able to create an evaluation from given source if it has already evaluated the same reputation of the target" do
|
30
30
|
RSEvaluation.create!(@attributes)
|
31
31
|
lambda {RSEvaluation.create!(@attributes)}.should raise_error
|
32
32
|
end
|
@@ -24,14 +24,14 @@ describe RSReputationMessage do
|
|
24
24
|
end
|
25
25
|
|
26
26
|
context "Validation" do
|
27
|
-
it "should not be able to create a message from given sender if it has
|
28
|
-
RSReputationMessage.create(:sender => @rep1, :receiver => @rep2).
|
29
|
-
RSReputationMessage.create(:sender => @rep1, :receiver => @rep2).
|
27
|
+
it "should not be able to create a message from given sender if it has already sent one to the same receiver" do
|
28
|
+
RSReputationMessage.create(:sender => @rep1, :receiver => @rep2).should be_valid
|
29
|
+
RSReputationMessage.create(:sender => @rep1, :receiver => @rep2).should_not be_valid
|
30
30
|
end
|
31
31
|
|
32
32
|
it "should have raise error if sender is neither RSEvaluation and RSReputation" do
|
33
33
|
RSReputationMessage.create(:sender => @user, :receiver => @rep2).errors[:sender].should_not be_nil
|
34
|
-
end
|
34
|
+
end
|
35
35
|
end
|
36
36
|
|
37
37
|
context "Association" do
|
@@ -41,18 +41,18 @@ describe RSReputation do
|
|
41
41
|
end
|
42
42
|
|
43
43
|
it "should be able to create reputation with process 'sum', 'average' and 'product'" do
|
44
|
-
RSReputation.create(:reputation_name => "karma1", :target_id => @user.id, :target_type => @user.class.to_s, :aggregated_by => 'sum').
|
45
|
-
RSReputation.create(:reputation_name => "karma2", :target_id => @user.id, :target_type => @user.class.to_s, :aggregated_by => 'average').
|
46
|
-
RSReputation.create(:reputation_name => "karma3", :target_id => @user.id, :target_type => @user.class.to_s, :aggregated_by => 'product').
|
44
|
+
RSReputation.create(:reputation_name => "karma1", :target_id => @user.id, :target_type => @user.class.to_s, :aggregated_by => 'sum').should be_valid
|
45
|
+
RSReputation.create(:reputation_name => "karma2", :target_id => @user.id, :target_type => @user.class.to_s, :aggregated_by => 'average').should be_valid
|
46
|
+
RSReputation.create(:reputation_name => "karma3", :target_id => @user.id, :target_type => @user.class.to_s, :aggregated_by => 'product').should be_valid
|
47
47
|
end
|
48
48
|
|
49
49
|
it "should not be able to create reputation with process other than 'sum', 'average' and 'product'" do
|
50
|
-
RSReputation.create(:reputation_name => "karma", :target_id => @user.id, :target_type => @user.class.to_s, :aggregated_by => 'invalid').
|
50
|
+
RSReputation.create(:reputation_name => "karma", :target_id => @user.id, :target_type => @user.class.to_s, :aggregated_by => 'invalid').should_not be_valid
|
51
51
|
end
|
52
52
|
|
53
53
|
it "should not be able to create reputation of the same name for the same target" do
|
54
|
-
RSReputation.create(:reputation_name => "karma", :target_id => @user.id, :target_type => @user.class.to_s, :aggregated_by => 'sum').
|
55
|
-
RSReputation.create(:reputation_name => "karma", :target_id => @user.id, :target_type => @user.class.to_s, :aggregated_by => 'sum').
|
54
|
+
RSReputation.create(:reputation_name => "karma", :target_id => @user.id, :target_type => @user.class.to_s, :aggregated_by => 'sum').should be_valid
|
55
|
+
RSReputation.create(:reputation_name => "karma", :target_id => @user.id, :target_type => @user.class.to_s, :aggregated_by => 'sum').should_not be_valid
|
56
56
|
end
|
57
57
|
end
|
58
58
|
|
@@ -48,11 +48,11 @@ describe ActiveRecord::Base do
|
|
48
48
|
|
49
49
|
it "should delete reputations if target is deleted" do
|
50
50
|
@question.add_evaluation(:total_votes, 5, @user)
|
51
|
-
|
52
|
-
|
51
|
+
reputation_count = RSReputation.count
|
52
|
+
message_count = RSReputationMessage.count
|
53
53
|
@question.destroy
|
54
|
-
RSReputation.count.should <
|
55
|
-
RSReputationMessage.count.should <
|
54
|
+
RSReputation.count.should < reputation_count
|
55
|
+
RSReputationMessage.count.should < message_count
|
56
56
|
end
|
57
57
|
|
58
58
|
it "should have declared default value if any" do
|
@@ -60,7 +60,7 @@ describe ActiveRecord::Base do
|
|
60
60
|
end
|
61
61
|
|
62
62
|
it "should raise exception if scope is not given" do
|
63
|
-
lambda{@phrase.add_evaluation(:difficulty_with_scope, 1
|
63
|
+
lambda{@phrase.add_evaluation(:difficulty_with_scope, 1)}.should raise_error(ArgumentError)
|
64
64
|
end
|
65
65
|
end
|
66
66
|
end
|
@@ -295,7 +295,7 @@ describe ActiveRecord::Base do
|
|
295
295
|
end
|
296
296
|
end
|
297
297
|
|
298
|
-
describe "#
|
298
|
+
describe "#update_evaluation" do
|
299
299
|
before :each do
|
300
300
|
@trans_ja.add_evaluation(:votes, 1, @user)
|
301
301
|
end
|
@@ -307,7 +307,7 @@ describe ActiveRecord::Base do
|
|
307
307
|
end
|
308
308
|
end
|
309
309
|
|
310
|
-
describe "#
|
310
|
+
describe "#delete_evaluation" do
|
311
311
|
before :each do
|
312
312
|
@trans_ja.add_evaluation(:votes, 1, @user)
|
313
313
|
end
|
@@ -343,7 +343,7 @@ describe ActiveRecord::Base do
|
|
343
343
|
end
|
344
344
|
end
|
345
345
|
|
346
|
-
describe "#
|
346
|
+
describe "#update_evaluation" do
|
347
347
|
before :each do
|
348
348
|
@trans_ja.add_evaluation(:votes, 1, @user)
|
349
349
|
@trans_de.add_evaluation(:votes, 3, @user)
|
@@ -356,7 +356,7 @@ describe ActiveRecord::Base do
|
|
356
356
|
end
|
357
357
|
end
|
358
358
|
|
359
|
-
describe "#
|
359
|
+
describe "#delete_evaluation" do
|
360
360
|
before :each do
|
361
361
|
@trans_ja.add_evaluation(:votes, 1, @user)
|
362
362
|
@trans_de.add_evaluation(:votes, 3, @user)
|
metadata
CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
|
|
4
4
|
prerelease: false
|
5
5
|
segments:
|
6
6
|
- 1
|
7
|
-
-
|
7
|
+
- 2
|
8
8
|
- 0
|
9
|
-
version: 1.
|
9
|
+
version: 1.2.0
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Katsuya Noguchi
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2012-
|
17
|
+
date: 2012-06-12 00:00:00 -07:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -110,6 +110,9 @@ files:
|
|
110
110
|
- README.md
|
111
111
|
- Rakefile
|
112
112
|
- lib/generators/reputation_system/reputation_system_generator.rb
|
113
|
+
- lib/generators/reputation_system/templates/add_evaluations_index.rb
|
114
|
+
- lib/generators/reputation_system/templates/add_reputation_messages_index.rb
|
115
|
+
- lib/generators/reputation_system/templates/add_reputations_index.rb
|
113
116
|
- lib/generators/reputation_system/templates/create_reputation_system.rb
|
114
117
|
- lib/models/rs_evaluation.rb
|
115
118
|
- lib/models/rs_reputation.rb
|