diff --git a/lib/reputation_system.rb b/lib/reputation_system.rb index 7c7d333..0356dc6 100644 --- a/lib/reputation_system.rb +++ b/lib/reputation_system.rb @@ -16,7 +16,6 @@ require 'reputation_system/base' require 'reputation_system/query' -require 'reputation_system/normalization' require 'reputation_system/evaluation' require 'reputation_system/network' require 'reputation_system/reputation' @@ -25,4 +24,4 @@ require 'models/rs_reputation' require 'models/rs_reputation_message' -ActiveRecord::Base.send(:include, ReputationSystem::Base) \ No newline at end of file +ActiveRecord::Base.send(:include, ReputationSystem::Base) diff --git a/lib/reputation_system/.normalization.rb.swp b/lib/reputation_system/.normalization.rb.swp new file mode 100644 index 0000000..dad8b4c Binary files /dev/null and b/lib/reputation_system/.normalization.rb.swp differ diff --git a/lib/reputation_system/.reputation.rb.swp b/lib/reputation_system/.reputation.rb.swp new file mode 100644 index 0000000..a3c0d6d Binary files /dev/null and b/lib/reputation_system/.reputation.rb.swp differ diff --git a/lib/reputation_system/base.rb b/lib/reputation_system/base.rb index d9087e5..c9a7249 100644 --- a/lib/reputation_system/base.rb +++ b/lib/reputation_system/base.rb @@ -51,7 +51,6 @@ def has_reputation(reputation_name, options) unless ancestors.include?(ReputationSystem::Reputation) has_many :reputations, :as => :target, :class_name => "RSReputation", :dependent => :destroy include ReputationSystem::Query - include ReputationSystem::Normalization include ReputationSystem::Reputation include ReputationSystem::Scope end diff --git a/lib/reputation_system/evaluation.rb b/lib/reputation_system/evaluation.rb index 6e46500..a71235a 100644 --- a/lib/reputation_system/evaluation.rb +++ b/lib/reputation_system/evaluation.rb @@ -17,7 +17,6 @@ module ReputationSystem module Evaluation def add_evaluation(reputation_name, value, source, *args) - raise ArgumentError, "#{reputation_name.to_s} is not defined for #{self.class.name}" unless ReputationSystem::Network.has_reputation_for?(self.class.name, reputation_name) scope = args.first srn = ReputationSystem::Network.get_scoped_reputation_name(self.class.name, reputation_name, scope) process = ReputationSystem::Network.get_reputation_def(self.class.name, srn)[:aggregated_by] @@ -27,63 +26,32 @@ def add_evaluation(reputation_name, value, source, *args) end def update_evaluation(reputation_name, value, source, *args) - raise ArgumentError, "#{reputation_name.to_s} is not defined for #{self.class.name}" unless ReputationSystem::Network.has_reputation_for?(self.class.name, reputation_name) - scope = args.first - srn = ReputationSystem::Network.get_scoped_reputation_name(self.class.name, reputation_name, scope) - evaluation = RSEvaluation.find_by_reputation_name_and_source_and_target(srn, source, self) - if evaluation.nil? - raise ArgumentError, "Given instance of #{source.class.name} has not evaluated #{reputation_name} of the instance of #{self.class.name} yet." - else - oldValue = evaluation.value - evaluation.value = value - evaluation.save! - process = ReputationSystem::Network.get_reputation_def(self.class.name, srn)[:aggregated_by] - rep = RSReputation.find_by_reputation_name_and_target(srn, self) - RSReputation.update_reputation_value_with_updated_source(rep, evaluation, oldValue, 1, process) - end + srn, evaluation = find_srn_and_evaluation!(reputation_name, source, args.first) + oldValue = evaluation.value + evaluation.value = value + evaluation.save! + process = ReputationSystem::Network.get_reputation_def(self.class.name, srn)[:aggregated_by] + rep = RSReputation.find_by_reputation_name_and_target(srn, self) + RSReputation.update_reputation_value_with_updated_source(rep, evaluation, oldValue, 1, process) end def add_or_update_evaluation(reputation_name, value, source, *args) - scope = args.first - srn = ReputationSystem::Network.get_scoped_reputation_name(self.class.name, reputation_name, scope) - evaluation = RSEvaluation.find_by_reputation_name_and_source_and_target(srn, source, self) - if evaluation.nil? - self.add_evaluation(reputation_name, value, source, scope) + srn, evaluation = find_srn_and_evaluation(reputation_name, source, args.first) + if RSEvaluation.exists? :reputation_name => srn, :source_id => source.id, :source_type => source.class.name, :target_id => self.id, :target_type => self.class.name + self.update_evaluation(reputation_name, value, source, *args) else - self.update_evaluation(reputation_name, value, source, scope) + self.add_evaluation(reputation_name, value, source, *args) end end def delete_evaluation(reputation_name, source, *args) - raise ArgumentError, "#{reputation_name.to_s} is not defined for #{self.class.name}" unless ReputationSystem::Network.has_reputation_for?(self.class.name, reputation_name) - scope = args.first - srn = ReputationSystem::Network.get_scoped_reputation_name(self.class.name, reputation_name, scope) - evaluation = RSEvaluation.find_by_reputation_name_and_source_and_target(srn, source, self) - unless evaluation.nil? - process = ReputationSystem::Network.get_reputation_def(self.class.name, srn)[:aggregated_by] - oldValue = evaluation.value - evaluation.value = process == :product ? 1 : 0 - rep = RSReputation.find_by_reputation_name_and_target(srn, self) - RSReputation.update_reputation_value_with_updated_source(rep, evaluation, oldValue, 1, process) - evaluation.destroy - end + srn, evaluation = find_srn_and_evaluation(reputation_name, source, args.first) + delete_evaluation_without_validation(srn, evaluation) if evaluation end def delete_evaluation!(reputation_name, source, *args) - raise ArgumentError, "#{reputation_name.to_s} is not defined for #{self.class.name}" unless ReputationSystem::Network.has_reputation_for?(self.class.name, reputation_name) - scope = args.first - srn = ReputationSystem::Network.get_scoped_reputation_name(self.class.name, reputation_name, scope) - evaluation = RSEvaluation.find_by_reputation_name_and_source_and_target(srn, source, self) - if evaluation.nil? - raise ArgumentError, "Given instance of #{source.class.name} has not evaluated #{reputation_name} of the instance of #{self.class.name} yet." - else - process = ReputationSystem::Network.get_reputation_def(self.class.name, srn)[:aggregated_by] - oldValue = evaluation.value - evaluation.value = process == :product ? 1 : 0 - rep = RSReputation.find_by_reputation_name_and_target(srn, self) - RSReputation.update_reputation_value_with_updated_source(rep, evaluation, oldValue, 1, process) - evaluation.destroy - end + srn, evaluation = find_srn_and_evaluation!(reputation_name, source, args.first) + delete_evaluation_without_validation(srn, evaluation) end def increase_evaluation(reputation_name, value, source, *args) @@ -95,6 +63,33 @@ def decrease_evaluation(reputation_name, value, source, *args) end protected + def find_srn_and_evaluation(reputation_name, source, scope) + srn = ReputationSystem::Network.get_scoped_reputation_name(self.class.name, reputation_name, scope) + evaluation = RSEvaluation.find_by_reputation_name_and_source_and_target(srn, source, self) + return srn, evaluation + end + + def find_srn_and_evaluation!(reputation_name, source, scope) + srn = ReputationSystem::Network.get_scoped_reputation_name(self.class.name, reputation_name, scope) + evaluation = find_evaluation!(reputation_name, srn, source) + return srn, evaluation + end + + def find_evaluation!(reputation_name, srn, source) + evaluation = RSEvaluation.find_by_reputation_name_and_source_and_target(srn, source, self) + raise ArgumentError, "Given instance of #{source.class.name} has not evaluated #{reputation_name} of the instance of #{self.class.name} yet." unless evaluation + evaluation + end + + def delete_evaluation_without_validation(srn, evaluation) + process = ReputationSystem::Network.get_reputation_def(self.class.name, srn)[:aggregated_by] + oldValue = evaluation.value + evaluation.value = process == :product ? 1 : 0 + rep = RSReputation.find_by_reputation_name_and_target(srn, self) + RSReputation.update_reputation_value_with_updated_source(rep, evaluation, oldValue, 1, process) + evaluation.destroy + end + def change_evaluation_value_by(reputation_name, value, source, *args) scope = args.first srn = ReputationSystem::Network.get_scoped_reputation_name(self.class.name, reputation_name, scope) diff --git a/lib/reputation_system/network.rb b/lib/reputation_system/network.rb index 0c925a1..88ffb99 100644 --- a/lib/reputation_system/network.rb +++ b/lib/reputation_system/network.rb @@ -82,6 +82,7 @@ def has_scope?(class_name, reputation_name, scope) end def get_scoped_reputation_name(class_name, reputation_name, scope) + raise ArgumentError, "#{reputation_name.to_s} is not defined for #{class_name}" unless has_reputation_for?(class_name, reputation_name) scope = scope.to_sym if scope validate_scope_necessity(class_name, reputation_name, scope) validate_scope_existence(class_name, reputation_name, scope) diff --git a/lib/reputation_system/normalization.rb b/lib/reputation_system/normalization.rb deleted file mode 100644 index 2d4155e..0000000 --- a/lib/reputation_system/normalization.rb +++ /dev/null @@ -1,50 +0,0 @@ -## -# Copyright 2012 Twitter, Inc -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -## - -module ReputationSystem - module Normalization - def normalized_reputation_value_for(reputation_name, *args) - scope = args.first - if !self.class.has_reputation_for?(reputation_name) - raise ArgumentError, "#{reputation_name} is not valid" - else - reputation_name = ReputationSystem::Network.get_scoped_reputation_name(self.class.name, reputation_name, scope) - process = ReputationSystem::Network.get_reputation_def(self.class.name, reputation_name)[:aggregated_by] - reputation = RSReputation.find_or_create_reputation(reputation_name, self, process) - reputation.normalized_value - end - end - - def activate_all_reputations - RSReputation.find(:all, :conditions => {:target_id => self.id, :target_type => self.class.name, :active => false}).each do |r| - r.active = true - r.save! - end - end - - def deactivate_all_reputations - RSReputation.find(:all, :conditions => {:target_id => self.id, :target_type => self.class.name, :active => true}).each do |r| - r.active = false - r.save! - end - end - - def reputations_activated?(reputation_name) - r = RSReputation.find(:first, :conditions => {:reputation_name => reputation_name.to_s, :target_id => self.id, :target_type => self.class.name}) - r ? r.active : false - end - end -end \ No newline at end of file diff --git a/lib/reputation_system/reputation.rb b/lib/reputation_system/reputation.rb index 51035fa..2441af3 100644 --- a/lib/reputation_system/reputation.rb +++ b/lib/reputation_system/reputation.rb @@ -17,17 +17,32 @@ module ReputationSystem module Reputation def reputation_value_for(reputation_name, *args) - scope = args.first - if !self.class.has_reputation_for?(reputation_name) - raise ArgumentError, "#{reputation_name} is not valid" - else - reputation_name = ReputationSystem::Network.get_scoped_reputation_name(self.class.name, reputation_name, scope) - process = ReputationSystem::Network.get_reputation_def(self.class.name, reputation_name)[:aggregated_by] - reputation = RSReputation.find_or_create_reputation(reputation_name, self, process) - reputation.value + find_reputation(reputation_name, args.first).value + end + + def normalized_reputation_value_for(reputation_name, *args) + find_reputation(reputation_name, args.first).normalized_value + end + + def activate_all_reputations + RSReputation.find(:all, :conditions => {:target_id => self.id, :target_type => self.class.name, :active => false}).each do |r| + r.active = true + r.save! + end + end + + def deactivate_all_reputations + RSReputation.find(:all, :conditions => {:target_id => self.id, :target_type => self.class.name, :active => true}).each do |r| + r.active = false + r.save! end end + def reputations_activated?(reputation_name) + r = RSReputation.find(:first, :conditions => {:reputation_name => reputation_name.to_s, :target_id => self.id, :target_type => self.class.name}) + r ? r.active : false + end + def rank_for(reputation_name, *args) scope = args.first my_value = self.reputation_value_for(reputation_name, scope) @@ -35,5 +50,13 @@ def rank_for(reputation_name, *args) :conditions => ["rs_reputations.value > ?", my_value] ) + 1 end + + protected + def find_reputation(reputation_name, scope) + raise ArgumentError, "#{reputation_name} is not valid" if !self.class.has_reputation_for?(reputation_name) + srn = ReputationSystem::Network.get_scoped_reputation_name(self.class.name, reputation_name, scope) + process = ReputationSystem::Network.get_reputation_def(self.class.name, srn)[:aggregated_by] + RSReputation.find_or_create_reputation(srn, self, process) + end end -end \ No newline at end of file +end diff --git a/spec/reputation_system/normalization_spec.rb b/spec/reputation_system/normalization_spec.rb index f06bed4..6ce0d4e 100644 --- a/spec/reputation_system/normalization_spec.rb +++ b/spec/reputation_system/normalization_spec.rb @@ -25,44 +25,4 @@ @phrase = Phrase.create!(:text => "One") end - describe "#normalized_reputation_value_for" do - it "should return 0 as if there is no data" do - @question.normalized_reputation_value_for(:total_votes).should == 0 - end - - it "should return appropriate value in case of valid input" do - question2 = Question.create!(:text => 'Does this work too?', :author_id => @user.id) - question3 = Question.create!(:text => 'Does this work too?', :author_id => @user.id) - @question.add_evaluation(:total_votes, 1, @user) - question2.add_evaluation(:total_votes, 2, @user) - question3.add_evaluation(:total_votes, 3, @user) - @question.normalized_reputation_value_for(:total_votes).should == 0 - question2.normalized_reputation_value_for(:total_votes).should == 0.5 - question3.normalized_reputation_value_for(:total_votes).should == 1 - end - - it "should raise exception if invalid reputation name is given" do - lambda {@question.normalized_reputation_value_for(:invalid)}.should raise_error(ArgumentError) - end - - it "should raise exception if scope is given for reputation with no scopes" do - lambda {@question.normalized_reputation_value_for(:difficulty, :s1)}.should raise_error(ArgumentError) - end - - it "should raise exception if scope is not given for reputation with scopes" do - lambda {@phrase.normalized_reputation_value_for(:difficulty_with_scope)}.should raise_error(ArgumentError) - end - end - - describe "#exclude_all_reputations_for_normalization" do - it "should activate all reputation" do - @question2 = Question.create!(:text => 'Does this work??', :author_id => @user.id) - @question2.add_evaluation(:total_votes, 70, @user) - @question.add_evaluation(:total_votes, 100, @user) - @question.deactivate_all_reputations - RSReputation.maximum(:value, :conditions => {:reputation_name => 'total_votes', :active => true}).should == 70 - @question.activate_all_reputations - RSReputation.maximum(:value, :conditions => {:reputation_name => 'total_votes', :active => true}).should == 100 - end - end -end \ No newline at end of file +end diff --git a/spec/reputation_system/reputation_spec.rb b/spec/reputation_system/reputation_spec.rb index 739cfca..0e897d2 100644 --- a/spec/reputation_system/reputation_spec.rb +++ b/spec/reputation_system/reputation_spec.rb @@ -116,4 +116,47 @@ end end end + + context "Normalization" do + describe "#normalized_reputation_value_for" do + it "should return 0 as if there is no data" do + @question.normalized_reputation_value_for(:total_votes).should == 0 + end + + it "should return appropriate value in case of valid input" do + question2 = Question.create!(:text => 'Does this work too?', :author_id => @user.id) + question3 = Question.create!(:text => 'Does this work too?', :author_id => @user.id) + @question.add_evaluation(:total_votes, 1, @user) + question2.add_evaluation(:total_votes, 2, @user) + question3.add_evaluation(:total_votes, 3, @user) + @question.normalized_reputation_value_for(:total_votes).should == 0 + question2.normalized_reputation_value_for(:total_votes).should == 0.5 + question3.normalized_reputation_value_for(:total_votes).should == 1 + end + + it "should raise exception if invalid reputation name is given" do + lambda {@question.normalized_reputation_value_for(:invalid)}.should raise_error(ArgumentError) + end + + it "should raise exception if scope is given for reputation with no scopes" do + lambda {@question.normalized_reputation_value_for(:difficulty, :s1)}.should raise_error(ArgumentError) + end + + it "should raise exception if scope is not given for reputation with scopes" do + lambda {@phrase.normalized_reputation_value_for(:difficulty_with_scope)}.should raise_error(ArgumentError) + end + end + + describe "#exclude_all_reputations_for_normalization" do + it "should activate all reputation" do + @question2 = Question.create!(:text => 'Does this work??', :author_id => @user.id) + @question2.add_evaluation(:total_votes, 70, @user) + @question.add_evaluation(:total_votes, 100, @user) + @question.deactivate_all_reputations + RSReputation.maximum(:value, :conditions => {:reputation_name => 'total_votes', :active => true}).should == 70 + @question.activate_all_reputations + RSReputation.maximum(:value, :conditions => {:reputation_name => 'total_votes', :active => true}).should == 100 + end + end + end end