From c6410e3e4be39e5dc54cda1d396faaa8f3d069d6 Mon Sep 17 00:00:00 2001 From: Rodrigo Souto Date: Mon, 11 Aug 2014 20:28:21 +0000 Subject: [PATCH] profile-suggestions: connections --- app/models/profile_suggestion.rb | 58 ++++++++++++++++++++++++++++++++++++++++++++-------------- app/models/suggestion_connection.rb | 6 ++++++ db/migrate/20140811141211_create_suggestion_connections.rb | 12 ++++++++++++ db/schema.rb | 8 +++++++- test/unit/profile_suggestion_test.rb | 71 +++++++++++++++++++---------------------------------------------------- 5 files changed, 88 insertions(+), 67 deletions(-) create mode 100644 app/models/suggestion_connection.rb create mode 100644 db/migrate/20140811141211_create_suggestion_connections.rb diff --git a/app/models/profile_suggestion.rb b/app/models/profile_suggestion.rb index a54a569..57e232d 100644 --- a/app/models/profile_suggestion.rb +++ b/app/models/profile_suggestion.rb @@ -4,6 +4,10 @@ class ProfileSuggestion < ActiveRecord::Base attr_accessible :person, :suggestion, :suggestion_type, :categories, :enabled + has_many :suggestion_connections, :foreign_key => 'suggestion_id' + has_many :profile_connections, :through => :suggestion_connections, :source => :connection, :source_type => 'Profile' + has_many :tag_connections, :through => :suggestion_connections, :source => :connection, :source_type => 'ActsAsTaggableOn::Tag' + before_create do |profile_suggestion| profile_suggestion.suggestion_type = self.suggestion.class.to_s end @@ -45,19 +49,19 @@ class ProfileSuggestion < ActiveRecord::Base RULES = { :people_with_common_communities => { - :threshold => 2, :weight => 1 + :threshold => 2, :weight => 1, :connection => 'Profile' }, :people_with_common_friends => { - :threshold => 2, :weight => 1 + :threshold => 2, :weight => 1, :connection => 'Profile' }, :people_with_common_tags => { - :threshold => 2, :weight => 1 + :threshold => 2, :weight => 1, :connection => 'ActsAsTaggableOn::Tag' }, :communities_with_common_friends => { - :threshold => 2, :weight => 1 + :threshold => 2, :weight => 1, :connection => 'Profile' }, :communities_with_common_tags => { - :threshold => 2, :weight => 1 + :threshold => 2, :weight => 1, :connection => 'ActsAsTaggableOn::Tag' } } @@ -118,9 +122,21 @@ class ProfileSuggestion < ActiveRecord::Base suggested_profiles.each do |suggested_profile| suggestion = person.profile_suggestions.find_or_initialize_by_suggestion_id(suggested_profile.id) RULES.each do |rule, options| - value = suggested_profile.send("#{rule}_count").to_i + begin + value = suggested_profile.send("#{rule}_count").to_i + rescue NoMethodError + next + end + connections = suggested_profile.send("#{rule}_connections") + if connections.present? + connections = connections[1..-2].split(',') + else + connections = [] + end suggestion.send("#{rule}=", value) - #TODO Create suggestion connections. + connections.each do |connection_id| + SuggestionConnection.create!(:suggestion => suggestion, :connection_id => connection_id, :connection_type => options[:connection]) + end suggestion.score += value * options[:weight] end suggestion.save! @@ -202,15 +218,25 @@ class ProfileSuggestion < ActiveRecord::Base end def self.all_suggestions(person) - select_string = "profiles.*, " + RULES.keys.map { |rule| "suggestions.#{counter(rule)} as #{counter(rule)}, suggestions.#{connections(rule)} as #{connections(rule)}" }.join(',') + select_string = ["profiles.*"] + suggestions_join = [] + where_string = [] + valid_rules = [] previous_rule = nil - suggestions_join = RULES.keys.map do |rule| + join_column = nil + RULES.each do |rule, options| + rule_select = self.send(rule, person) + next if !rule_select.present? + + valid_rules << rule + select_string << "suggestions.#{counter(rule)} as #{counter(rule)}, suggestions.#{connections(rule)} as #{connections(rule)}" + where_string << "#{counter(rule)} >= #{options[:threshold]}" rule_select = " (SELECT profiles.id as #{profile_id(rule)}, #{rule}_sub.#{counter(rule)} as #{counter(rule)}, #{rule}_sub.#{connections(rule)} as #{connections(rule)} FROM profiles - LEFT OUTER JOIN (#{self.send(rule, person)}) as #{rule}_sub + LEFT OUTER JOIN (#{rule_select}) as #{rule}_sub ON profiles.id = #{rule}_sub.#{profile_id(rule)}) AS #{rule}" if previous_rule.nil? @@ -220,10 +246,14 @@ class ProfileSuggestion < ActiveRecord::Base ON #{previous_rule}.#{profile_id(previous_rule)} = #{rule}.#{profile_id(rule)}" end previous_rule = rule - result - end.join(' ') - join_string = "INNER JOIN (SELECT * FROM #{suggestions_join}) AS suggestions ON profiles.id = suggestions.#{profile_id(RULES.keys.first)}" - where_string = RULES.map { |rule, options| "#{counter(rule)} >= #{options[:threshold]}"}.join(' OR ') + suggestions_join << result + end + + return if valid_rules.blank? + + select_string = select_string.compact.join(',') + join_string = "INNER JOIN (SELECT * FROM #{suggestions_join.compact.join(' ')}) AS suggestions ON profiles.id = suggestions.#{profile_id(valid_rules.first)}" + where_string = where_string.compact.join(' OR ') person.environment.profiles. select(select_string). diff --git a/app/models/suggestion_connection.rb b/app/models/suggestion_connection.rb new file mode 100644 index 0000000..fac6898 --- /dev/null +++ b/app/models/suggestion_connection.rb @@ -0,0 +1,6 @@ +class SuggestionConnection < ActiveRecord::Base + attr_accessible :suggestion, :connection_type, :connection_id + + belongs_to :suggestion, :class_name => 'ProfileSuggestion', :foreign_key => 'suggestion_id' + belongs_to :connection, :polymorphic => true +end diff --git a/db/migrate/20140811141211_create_suggestion_connections.rb b/db/migrate/20140811141211_create_suggestion_connections.rb new file mode 100644 index 0000000..ef28302 --- /dev/null +++ b/db/migrate/20140811141211_create_suggestion_connections.rb @@ -0,0 +1,12 @@ +class CreateSuggestionConnections < ActiveRecord::Migration + def up + create_table :suggestion_connections do |t| + t.references :suggestion, :null => false + t.references :connection, :polymorphic => true, :null => false + end + end + + def down + drop_table :suggestion_connections + end +end diff --git a/db/schema.rb b/db/schema.rb index 097693e..12ac81d 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20140805205626) do +ActiveRecord::Schema.define(:version => 20140811141211) do create_table "abuse_reports", :force => true do |t| t.integer "reporter_id" @@ -603,6 +603,12 @@ ActiveRecord::Schema.define(:version => 20140805205626) do add_index "sessions", ["session_id"], :name => "index_sessions_on_session_id" add_index "sessions", ["updated_at"], :name => "index_sessions_on_updated_at" + create_table "suggestion_connections", :force => true do |t| + t.integer "suggestion_id", :null => false + t.integer "connection_id", :null => false + t.string "connection_type", :null => false + end + create_table "taggings", :force => true do |t| t.integer "tag_id" t.integer "taggable_id" diff --git a/test/unit/profile_suggestion_test.rb b/test/unit/profile_suggestion_test.rb index ec2471a..d2cd3e5 100644 --- a/test/unit/profile_suggestion_test.rb +++ b/test/unit/profile_suggestion_test.rb @@ -57,7 +57,10 @@ class ProfileSuggestionTest < ActiveSupport::TestCase p4.add_friend(p6) ; p6.add_friend(p4) p2.add_friend(p7) ; p7.add_friend(p2) - assert_equivalent ProfileSuggestion.people_with_common_friends(p1), [p5,p6] + suggestions = ProfileSuggestion.calculate_suggestions(p1) + + assert_includes suggestions, p5 + assert_includes suggestions, p6 end should 'calculate people with common_communities' do @@ -81,7 +84,10 @@ class ProfileSuggestionTest < ActiveSupport::TestCase c3.add_member(p4) c4.add_member(p5) - assert_equivalent ProfileSuggestion.people_with_common_communities(p1), [p2,p4] + suggestions = ProfileSuggestion.calculate_suggestions(p1) + + assert_includes suggestions, p2 + assert_includes suggestions, p4 end should 'calculate people with common_tags' do @@ -121,7 +127,10 @@ class ProfileSuggestionTest < ActiveSupport::TestCase a52.tag_list = ['onivorism', 'facism'] a52.save! - assert_equivalent ProfileSuggestion.people_with_common_tags(p1), [p2, p3] + suggestions = ProfileSuggestion.calculate_suggestions(p1) + + assert_includes suggestions, p2 + assert_includes suggestions, p3 end should 'calculate communities with common_friends' do @@ -146,7 +155,10 @@ class ProfileSuggestionTest < ActiveSupport::TestCase c3.add_member(p2) c4.add_member(p3) - assert_equivalent ProfileSuggestion.communities_with_common_friends(p1), [c1,c2] + suggestions = ProfileSuggestion.calculate_suggestions(p1) + + assert_includes suggestions, c1 + assert_includes suggestions, c2 end should 'calculate communities with common_tags' do @@ -186,39 +198,10 @@ class ProfileSuggestionTest < ActiveSupport::TestCase a52.tag_list = ['onivorism', 'facism'] a52.save! - assert_equivalent ProfileSuggestion.communities_with_common_tags(p1), [p2, p3] - end + suggestions = ProfileSuggestion.calculate_suggestions(p1) - should 'register suggestions' do - person = create_user('person').person - rule = ProfileSuggestion::RULES.sample - p1 = fast_create(Profile) - p2 = fast_create(Profile) - p3 = fast_create(Profile) - # Hack to simulate a common_count that generated on the rules - suggestions = Profile.select('profiles.*, profiles.id as common_count').where("id in (#{[p1,p2,p3].map(&:id).join(',')})") - - assert_difference 'ProfileSuggestion.count', 3 do - ProfileSuggestion.register_suggestions(person, suggestions, rule) - end - assert_no_difference 'ProfileSuggestion.count' do - s1 = ProfileSuggestion.find_or_initialize_by_suggestion_id(p1.id) - assert_equal p1, s1.suggestion - s2 = ProfileSuggestion.find_or_initialize_by_suggestion_id(p2.id) - assert_equal p2, s2.suggestion - s3 = ProfileSuggestion.find_or_initialize_by_suggestion_id(p3.id) - assert_equal p3, s3.suggestion - end - end - - should 'calculate every rule suggestion' do - person = create_user('person').person - ProfileSuggestion::RULES.each do |rule| - suggestion = fast_create(Profile) - ProfileSuggestion.expects(rule).returns([suggestion]) - ProfileSuggestion.expects(:register_suggestions).with(person, [suggestion], rule).returns(true) - end - ProfileSuggestion.calculate_suggestions(person) + assert_includes suggestions, p2 + assert_includes suggestions, p3 end #FIXME This might not be necessary anymore... @@ -303,22 +286,6 @@ class ProfileSuggestionTest < ActiveSupport::TestCase # end # end - should 'register only new suggestions' do - person = create_user('person').person - ProfileSuggestion::SUGGESTIONS_BY_RULE.times do - ProfileSuggestion.create!(:person => person, :suggestion => fast_create(Person)) - end - - person.reload - new_suggestion = fast_create(Person) - ids = (person.suggested_people + [new_suggestion]).map(&:id).join(',') - suggested_profiles = Profile.select('profiles.*, profiles.id as common_count').where("profiles.id IN (#{ids})") - - assert_difference 'ProfileSuggestion.count', 1 do - ProfileSuggestion.register_suggestions(person, suggested_profiles, 'people_with_common_friends') - end - end - should 'calculate new suggestions when number of available suggestions reaches the min_limit' do person = create_user('person').person (ProfileSuggestion::MIN_LIMIT + 1).times do -- libgit2 0.21.2