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,35 @@
|
|
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 'rails/generators'
|
18
|
+
require 'rails/generators/migration'
|
19
|
+
require 'rails/generators/active_record'
|
20
|
+
|
21
|
+
class ReputationSystemGenerator < Rails::Generators::Base
|
22
|
+
include Rails::Generators::Migration
|
23
|
+
|
24
|
+
desc "Creates migration files required by reputation system gem."
|
25
|
+
|
26
|
+
self.source_paths << File.join(File.dirname(__FILE__), 'templates')
|
27
|
+
|
28
|
+
def self.next_migration_number(path)
|
29
|
+
ActiveRecord::Generators::Base.next_migration_number(path)
|
30
|
+
end
|
31
|
+
|
32
|
+
def create_migration_files
|
33
|
+
migration_template 'create_reputation_system.rb', 'db/migrate/create_reputation_system.rb'
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,59 @@
|
|
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 CreateReputationSystem < ActiveRecord::Migration
|
18
|
+
def self.up
|
19
|
+
create_table :rs_evaluations do |t|
|
20
|
+
t.string :reputation_name
|
21
|
+
t.references :source, :polymorphic => true
|
22
|
+
t.references :target, :polymorphic => true
|
23
|
+
t.float :value, :default => 0
|
24
|
+
t.timestamps
|
25
|
+
end
|
26
|
+
|
27
|
+
add_index :rs_evaluations, :reputation_name
|
28
|
+
add_index :rs_evaluations, [:target_id, :target_type]
|
29
|
+
add_index :rs_evaluations, [:source_id, :source_type]
|
30
|
+
|
31
|
+
create_table :rs_reputations do |t|
|
32
|
+
t.string :reputation_name
|
33
|
+
t.float :value, :default => 0
|
34
|
+
t.string :aggregated_by
|
35
|
+
t.references :target, :polymorphic => true
|
36
|
+
t.boolean :active, :default => true
|
37
|
+
t.timestamps
|
38
|
+
end
|
39
|
+
|
40
|
+
add_index :rs_reputations, :reputation_name
|
41
|
+
add_index :rs_reputations, [:target_id, :target_type]
|
42
|
+
|
43
|
+
create_table :rs_reputation_messages do |t|
|
44
|
+
t.references :sender, :polymorphic => true
|
45
|
+
t.integer :receiver_id
|
46
|
+
t.float :weight, :default => 1
|
47
|
+
t.timestamps
|
48
|
+
end
|
49
|
+
|
50
|
+
add_index :rs_reputation_messages, [:sender_id, :sender_type]
|
51
|
+
add_index :rs_reputation_messages, :receiver_id
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.down
|
55
|
+
drop_table :rs_evaluations
|
56
|
+
drop_table :rs_reputations
|
57
|
+
drop_table :rs_reputation_messages
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,52 @@
|
|
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 RSEvaluation < ActiveRecord::Base
|
18
|
+
belongs_to :source, :polymorphic => true
|
19
|
+
belongs_to :target, :polymorphic => true
|
20
|
+
has_one :sent_messages, :as => :sender, :class_name => 'RSReputationMessage', :dependent => :destroy
|
21
|
+
|
22
|
+
attr_accessible :reputation_name, :value, :source, :source_id, :source_type, :target, :target_id, :target_type
|
23
|
+
|
24
|
+
# the same source cannot evaluate the same target more than once.
|
25
|
+
validates_uniqueness_of :source_id, :scope => [:reputation_name, :source_type, :target_id, :target_type]
|
26
|
+
validate :source_must_be_defined_for_reputation_in_network
|
27
|
+
|
28
|
+
def self.find_by_reputation_name_and_source_and_target(reputation_name, source, target)
|
29
|
+
RSEvaluation.find(:first,
|
30
|
+
:conditions => {:reputation_name => reputation_name.to_s,
|
31
|
+
:source_id => source.id,
|
32
|
+
:source_type => source.class.name,
|
33
|
+
:target_id => target.id,
|
34
|
+
:target_type => target.class.name
|
35
|
+
})
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.create_evaluation(reputation_name, value, source, target)
|
39
|
+
reputation_name = reputation_name.to_sym
|
40
|
+
RSEvaluation.create!(:reputation_name => reputation_name.to_s, :value => value,
|
41
|
+
:source_id => source.id, :source_type => source.class.name,
|
42
|
+
:target_id => target.id, :target_type => target.class.name)
|
43
|
+
end
|
44
|
+
|
45
|
+
protected
|
46
|
+
|
47
|
+
def source_must_be_defined_for_reputation_in_network
|
48
|
+
unless source_type == ReputationSystem::Network.get_reputation_def(target_type, reputation_name)[:source].to_s.camelize
|
49
|
+
errors.add(:source_type, "#{source_type} is not source of #{reputation_name} reputation")
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,181 @@
|
|
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 RSReputation < ActiveRecord::Base
|
18
|
+
belongs_to :target, :polymorphic => true
|
19
|
+
has_many :received_messages, :class_name => 'RSReputationMessage', :foreign_key => :receiver_id, :dependent => :destroy do
|
20
|
+
def from(sender)
|
21
|
+
self.find_by_sender_id_and_sender_type(sender.id, sender.class.to_s)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
has_many :sent_messages, :as => :sender, :class_name => 'RSReputationMessage', :dependent => :destroy
|
25
|
+
|
26
|
+
attr_accessible :reputation_name, :value, :aggregated_by, :active, :target, :target_id, :target_type, :received_messages
|
27
|
+
|
28
|
+
before_save :change_zero_value_in_case_of_product_process
|
29
|
+
before_create
|
30
|
+
|
31
|
+
VALID_PROCESSES = ['sum', 'average', 'product']
|
32
|
+
validates_inclusion_of :aggregated_by, :in => VALID_PROCESSES, :message => "Value chosen for aggregated_by is not valid process"
|
33
|
+
validates_uniqueness_of :reputation_name, :scope => [:target_id, :target_type]
|
34
|
+
|
35
|
+
def self.find_by_reputation_name_and_target(reputation_name, target)
|
36
|
+
RSReputation.find_by_reputation_name_and_target_id_and_target_type(reputation_name.to_s, target.id, target.class.name)
|
37
|
+
end
|
38
|
+
|
39
|
+
# All external access to reputation should use this since they are created lazily.
|
40
|
+
def self.find_or_create_reputation(reputation_name, target, process)
|
41
|
+
rep = find_by_reputation_name_and_target(reputation_name, target)
|
42
|
+
rep ? rep : create_reputation(reputation_name, target, process)
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.create_reputation(reputation_name, target, process)
|
46
|
+
create_options = {:reputation_name => reputation_name.to_s, :target_id => target.id,
|
47
|
+
:target_type => target.class.name, :aggregated_by => process.to_s}
|
48
|
+
default_value = ReputationSystem::Network.get_reputation_def(target.class.name, reputation_name)[:init_value]
|
49
|
+
create_options.merge!(:value => default_value) if default_value
|
50
|
+
rep = create(create_options)
|
51
|
+
initialize_reputation_value(rep, target, process)
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.update_reputation_value_with_new_source(rep, source, weight, process)
|
55
|
+
weight = 1 unless weight # weight is 1 by default.
|
56
|
+
size = rep.received_messages.size
|
57
|
+
valueBeforeUpdate = size > 0 ? rep.value : nil
|
58
|
+
newValue = source.value
|
59
|
+
case process.to_sym
|
60
|
+
when :sum
|
61
|
+
rep.value += (newValue * weight)
|
62
|
+
when :average
|
63
|
+
rep.value = (rep.value * size + newValue * weight) / (size + 1)
|
64
|
+
when :product
|
65
|
+
rep.value *= (newValue * weight)
|
66
|
+
else
|
67
|
+
raise ArgumentError, "#{process} process is not supported yet"
|
68
|
+
end
|
69
|
+
rep.save!
|
70
|
+
RSReputationMessage.add_reputation_message_if_not_exist(source, rep)
|
71
|
+
propagate_updated_reputation_value(rep, valueBeforeUpdate) if rep.target
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.update_reputation_value_with_updated_source(rep, source, oldValue, weight, process)
|
75
|
+
weight = 1 unless weight # weight is 1 by default.\
|
76
|
+
size = rep.received_messages.size
|
77
|
+
valueBeforeUpdate = size > 0 ? rep.value : nil
|
78
|
+
newValue = source.value
|
79
|
+
case process.to_sym
|
80
|
+
when :sum
|
81
|
+
rep.value = rep.value + (newValue - oldValue) * weight
|
82
|
+
when :average
|
83
|
+
rep.value = rep.value + ((newValue - oldValue) * weight) / size
|
84
|
+
when :product
|
85
|
+
rep.value = (rep.value * newValue) / oldValue
|
86
|
+
else
|
87
|
+
raise ArgumentError, "#{process} process is not supported yet"
|
88
|
+
end
|
89
|
+
rep.save!
|
90
|
+
propagate_updated_reputation_value(rep, valueBeforeUpdate) if rep.target
|
91
|
+
end
|
92
|
+
|
93
|
+
def normalized_value
|
94
|
+
if self.active == 1 || self.active == true
|
95
|
+
max = RSReputation.max(self.reputation_name, self.target_type)
|
96
|
+
min = RSReputation.min(self.reputation_name, self.target_type)
|
97
|
+
if max && min
|
98
|
+
range = max - min
|
99
|
+
range == 0 ? 0 : (self.value - min) / range
|
100
|
+
else
|
101
|
+
0
|
102
|
+
end
|
103
|
+
else
|
104
|
+
0
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
protected
|
109
|
+
|
110
|
+
# Updates reputation value for new reputation if its source already exist.
|
111
|
+
def self.initialize_reputation_value(receiver, target, process)
|
112
|
+
name = receiver.reputation_name
|
113
|
+
unless ReputationSystem::Network.is_primary_reputation?(target.class.name, name)
|
114
|
+
sender_defs = ReputationSystem::Network.get_reputation_def(target.class.name, name)[:source]
|
115
|
+
sender_defs.each do |sd|
|
116
|
+
sender_targets = target.get_attributes_of(sd)
|
117
|
+
sender_targets.each do |st|
|
118
|
+
update_reputation_if_source_exist(sd, st, receiver, process) if receiver.target
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
receiver
|
123
|
+
end
|
124
|
+
|
125
|
+
# Propagates updated reputation value to the reputations whose source is the updated reputation.
|
126
|
+
def self.propagate_updated_reputation_value(sender, oldValue)
|
127
|
+
sender_name = sender.reputation_name.to_sym
|
128
|
+
receiver_defs = ReputationSystem::Network.get_reputation_def(sender.target.class.name, sender_name)[:source_of]
|
129
|
+
if receiver_defs
|
130
|
+
receiver_defs.each do |rd|
|
131
|
+
receiver_targets = sender.target.get_attributes_of(rd)
|
132
|
+
receiver_targets.each do |rt|
|
133
|
+
scope = sender.target.evaluate_reputation_scope(rd[:scope])
|
134
|
+
srn = ReputationSystem::Network.get_scoped_reputation_name(rt.class.name, rd[:reputation], scope)
|
135
|
+
process = ReputationSystem::Network.get_reputation_def(rt.class.name, srn)[:aggregated_by]
|
136
|
+
rep = find_by_reputation_name_and_target(srn, rt)
|
137
|
+
if rep
|
138
|
+
weight = ReputationSystem::Network.get_weight_of_source_from_reputation_name_of_target(rt, sender_name, srn)
|
139
|
+
unless oldValue
|
140
|
+
update_reputation_value_with_new_source(rep, sender, weight, process)
|
141
|
+
else
|
142
|
+
update_reputation_value_with_updated_source(rep, sender, oldValue, weight, process)
|
143
|
+
end
|
144
|
+
# If r is new then value update will be done when it is initialized.
|
145
|
+
else
|
146
|
+
create_reputation(srn, rt, process)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def self.update_reputation_if_source_exist(sd, st, receiver, process)
|
154
|
+
scope = receiver.target.evaluate_reputation_scope(sd[:scope])
|
155
|
+
srn = ReputationSystem::Network.get_scoped_reputation_name(st.class.name, sd[:reputation], scope)
|
156
|
+
source = find_by_reputation_name_and_target(srn, st)
|
157
|
+
if source
|
158
|
+
update_reputation_value_with_new_source(receiver, source, sd[:weight], process)
|
159
|
+
RSReputationMessage.add_reputation_message_if_not_exist(source, receiver)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def self.max(reputation_name, target_type)
|
164
|
+
RSReputation.maximum(:value,
|
165
|
+
:conditions => {:reputation_name => reputation_name.to_s, :target_type => target_type, :active => true})
|
166
|
+
end
|
167
|
+
|
168
|
+
def self.min(reputation_name, target_type)
|
169
|
+
RSReputation.minimum(:value,
|
170
|
+
:conditions => {:reputation_name => reputation_name.to_s, :target_type => target_type, :active => true})
|
171
|
+
end
|
172
|
+
|
173
|
+
def change_zero_value_in_case_of_product_process
|
174
|
+
self.value = 1 if self.value == 0 && self.aggregated_by == "product"
|
175
|
+
end
|
176
|
+
|
177
|
+
def remove_associated_messages
|
178
|
+
RSReputationMessage.delete_all(:sender_type => self.class.name, :sender_id => self.id)
|
179
|
+
RSReputationMessage.delete_all(:receiver_id => self.id)
|
180
|
+
end
|
181
|
+
end
|
@@ -0,0 +1,46 @@
|
|
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 RSReputationMessage < ActiveRecord::Base
|
18
|
+
belongs_to :sender, :polymorphic => true
|
19
|
+
belongs_to :receiver, :class_name => 'RSReputation'
|
20
|
+
|
21
|
+
attr_accessible :weight, :sender, :receiver
|
22
|
+
|
23
|
+
# The same sender cannot send massage to the same receiver more than once.
|
24
|
+
validates_uniqueness_of :receiver_id, :scope => [:sender_id, :sender_type]
|
25
|
+
validate :sender_must_be_evaluation_or_reputation
|
26
|
+
|
27
|
+
after_destroy :delete_sender_if_evaluation
|
28
|
+
|
29
|
+
def self.add_reputation_message_if_not_exist(sender, receiver)
|
30
|
+
rm = create(:sender => sender, :receiver => receiver)
|
31
|
+
receiver.received_messages.push rm if rm.valid?
|
32
|
+
end
|
33
|
+
|
34
|
+
protected
|
35
|
+
|
36
|
+
def delete_sender_if_evaluation
|
37
|
+
sender.destroy if sender.is_a?(RSEvaluation)
|
38
|
+
end
|
39
|
+
|
40
|
+
def sender_must_be_evaluation_or_reputation
|
41
|
+
unless sender.is_a?(RSEvaluation) || sender.is_a?(RSReputation)
|
42
|
+
errors.add(:sender, "must be an evaluation or a reputation")
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
@@ -0,0 +1,28 @@
|
|
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 'reputation_system/base'
|
18
|
+
require 'reputation_system/query'
|
19
|
+
require 'reputation_system/normalization'
|
20
|
+
require 'reputation_system/evaluation'
|
21
|
+
require 'reputation_system/network'
|
22
|
+
require 'reputation_system/reputation'
|
23
|
+
require 'reputation_system/scope'
|
24
|
+
require 'models/rs_evaluation'
|
25
|
+
require 'models/rs_reputation'
|
26
|
+
require 'models/rs_reputation_message'
|
27
|
+
|
28
|
+
ActiveRecord::Base.send(:include, ReputationSystem::Base)
|
@@ -0,0 +1,70 @@
|
|
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
|
+
module ReputationSystem
|
18
|
+
module Base
|
19
|
+
def self.included(klass)
|
20
|
+
klass.extend ClassMethods
|
21
|
+
end
|
22
|
+
|
23
|
+
def get_attributes_of(reputation)
|
24
|
+
of = reputation[:of]
|
25
|
+
attrs = reputation[:of] == :self ? self : self.instance_eval(of.to_s) if of.is_a?(String) || of.is_a?(Symbol)
|
26
|
+
attrs = self.instance_exec(self, &of) if of.is_a?(Proc)
|
27
|
+
attrs = [attrs] unless attrs.is_a? Array
|
28
|
+
attrs
|
29
|
+
end
|
30
|
+
|
31
|
+
def evaluate_reputation_scope(scope)
|
32
|
+
if scope
|
33
|
+
if self.respond_to? scope
|
34
|
+
self.send(scope)
|
35
|
+
else
|
36
|
+
scope
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
module ClassMethods
|
42
|
+
def has_reputation(reputation_name, options)
|
43
|
+
has_valid_input = reputation_name && options[:aggregated_by] && options[:source]
|
44
|
+
|
45
|
+
raise ArgumentError, "has_reputation method received invalid arguments." unless has_valid_input
|
46
|
+
# Overwrites reputation if the same reputation name is declared in the same model.
|
47
|
+
# TODO: This should raise exception instead while allowing Rails app to reload in dev mode.
|
48
|
+
ReputationSystem::Network.remove_reputation_def(name, reputation_name) if has_reputation_for?(reputation_name)
|
49
|
+
|
50
|
+
# If it is first time to be called
|
51
|
+
unless ancestors.include?(ReputationSystem::Reputation)
|
52
|
+
has_many :reputations, :as => :target, :class_name => "RSReputation", :dependent => :destroy
|
53
|
+
include ReputationSystem::Query
|
54
|
+
include ReputationSystem::Normalization
|
55
|
+
include ReputationSystem::Reputation
|
56
|
+
include ReputationSystem::Scope
|
57
|
+
end
|
58
|
+
|
59
|
+
ReputationSystem::Network.add_reputation_def(name, reputation_name, options)
|
60
|
+
|
61
|
+
# evaluation related methods are defined only for primary reputations
|
62
|
+
include ReputationSystem::Evaluation if ReputationSystem::Network.is_primary_reputation?(name, reputation_name) && !ancestors.include?(ReputationSystem::Evaluation)
|
63
|
+
end
|
64
|
+
|
65
|
+
def has_reputation_for?(reputation_name)
|
66
|
+
ReputationSystem::Network.has_reputation_for?(name, reputation_name)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|