From 305492c2c89e42f1d59f2df1c9932bd2ef8e9811 Mon Sep 17 00:00:00 2001 From: Ben Halpern Date: Wed, 17 Oct 2018 11:53:28 -0400 Subject: [PATCH] Add similarity score for users (#942) --- app/controllers/internal/users_controller.rb | 6 +- app/labor/user_similarity.rb | 63 +++++++++++++++++++ app/views/internal/users/show.html.erb | 65 ++++++++++---------- spec/labor/user_similarity_spec.rb | 29 +++++++++ spec/mailers/notify_mailer_spec.rb | 8 +++ 5 files changed, 132 insertions(+), 39 deletions(-) create mode 100644 app/labor/user_similarity.rb create mode 100644 spec/labor/user_similarity_spec.rb diff --git a/app/controllers/internal/users_controller.rb b/app/controllers/internal/users_controller.rb index 2a6408afc..610e431fb 100644 --- a/app/controllers/internal/users_controller.rb +++ b/app/controllers/internal/users_controller.rb @@ -33,11 +33,7 @@ class Internal::UsersController < Internal::ApplicationController handle_mentorship add_note @user.update!(user_params) - if params[:quick_match] - redirect_to "/internal/users/unmatched_mentee" - else - redirect_to "/internal/users/#{@user.id}" - end + redirect_to "/internal/users/unmatched_mentee" end def handle_mentorship diff --git a/app/labor/user_similarity.rb b/app/labor/user_similarity.rb new file mode 100644 index 000000000..dd5bc1619 --- /dev/null +++ b/app/labor/user_similarity.rb @@ -0,0 +1,63 @@ +class UserSimilarity + STOP_WORDS = %w( + a cannot into our thus about co is ours to above + could it ourselves together across down its out too + after during itself over toward afterwards each last own + towards again eg latter per under against either latterly + perhaps until all else least rather up almost elsewhere + less same upon alone enough ltd seem us along etc + many seemed very already even may seeming via also ever + me seems was although every meanwhile several we always + everyone might she well among everything more should were + amongst everywhere moreover since what an except most so + whatever and few mostly some when another first much + somehow whence any for must someone whenever anyhow + former my something where anyone formerly myself sometime + whereafter anything from namely sometimes whereas anywhere + further neither somewhere whereby are had never still + wherein around has nevertheless such whereupon as have + next than wherever at he no that whether be hence + nobody the whither became her none their which because + here noone them while become hereafter nor themselves who + becomes hereby not then whoever becoming herein nothing + thence whole been hereupon now there whom before hers + nowhere thereafter whose beforehand herself of thereby why + behind him off therefore will being himself often therein + with below his on thereupon within beside how once + these without besides however one they would between i + only this yet beyond ie onto those you both if or + though your but in other through yours by inc others + throughout yourself can indeed otherwise thru yourselves' + ).freeze + + attr_accessor :first_user, :second_user + def initialize(first_user, second_user) + @first_user = first_user + @second_user = second_user + end + + def score + mentorship_score + profile_score + tag_score + end + + def mentorship_score + (first_user.mentee_description.to_s.tr("0-9", "").split(" ") & second_user.mentor_description.to_s.tr("0-9", "").split(" ") - STOP_WORDS).size + + (first_user.mentor_description.to_s.tr("0-9", "").split(" ") & second_user.mentee_description.to_s.tr("0-9", "").split(" ") - STOP_WORDS).size + end + + def profile_score + summary_score = (first_user.summary.to_s.split(" ") & second_user.summary.to_s.split(" ") - STOP_WORDS).size + mostly_work_with_score = (first_user.mostly_work_with.to_s.split(" ") & second_user.mostly_work_with.to_s.split(" ") - STOP_WORDS).size + summary_score + mostly_work_with_score + end + + def tag_score + tag_intersection.delete("discuss") + tag_intersection.delete("hiring") + tag_intersection.size * 2 + end + + def tag_intersection + first_user.cached_followed_tag_names & second_user.cached_followed_tag_names + end +end diff --git a/app/views/internal/users/show.html.erb b/app/views/internal/users/show.html.erb index 08186521e..c7c00905f 100644 --- a/app/views/internal/users/show.html.erb +++ b/app/views/internal/users/show.html.erb @@ -108,41 +108,38 @@ <% count = 0 %> <% MentorRelationship. unmatched_mentors.where.not(mentor_form_updated_at: nil, id: @user.id). - order("mentor_form_updated_at DESC").each do |possible_mentor| %> - <% break if count > 20 %> - <% tag_intersection = ( @user.cached_followed_tag_names & possible_mentor.cached_followed_tag_names ) %> - <% if tag_intersection.any? %> - <% count = count + 1 %> -
- -
-

Tag Intersection

- <%= tag_intersection.join(", ") %> -
-
-

Mentor description

- <%= possible_mentor.mentor_description %> -
-
-

Skills/Languages

- <%= possible_mentor.mostly_work_with %> -
-
-

Action

- <%= form_for ([:internal, @user]) do |f| %> - <%= f.hidden_field :quick_match, value: true %> - <%= f.hidden_field :add_mentor, value: possible_mentor.id %> - <%= f.submit "Match!" %> - <% end %> -
+ order("mentor_form_updated_at DESC").limit(175). + sort_by { |possible_mentor| UserSimilarity.new(@user, possible_mentor).score }.reverse. + first(20).each do |possible_mentor| %> +
+ - <% end %> +
+

Tag Intersection

+ <%= UserSimilarity.new(@user, possible_mentor).tag_intersection.join(", ") %> +
+
+

Mentor description

+ <%= possible_mentor.mentor_description %> +
+
+

Skills/Languages

+ <%= possible_mentor.mostly_work_with %> +
+
+

Action

+ <%= form_for ([:internal, @user]) do |f| %> + <%= f.hidden_field :quick_match, value: true %> + <%= f.hidden_field :add_mentor, value: possible_mentor.id %> + <%= f.submit "Match!" %> + <% end %> +
+
<% end %> <% end %> diff --git a/spec/labor/user_similarity_spec.rb b/spec/labor/user_similarity_spec.rb new file mode 100644 index 000000000..4a04037aa --- /dev/null +++ b/spec/labor/user_similarity_spec.rb @@ -0,0 +1,29 @@ +require "rails_helper" + +RSpec.describe UserSimilarity, vcr: {} do + let(:user) { create(:user, summary: "I like ruby and JavaScript and Go") } + let(:similar_user) { create(:user, summary: "I like JavaScript and Go") } + let(:dissimilar_user) { create(:user, summary: "I like Haskell and functional programming") } + + it "It returns similar user" do + simialar_score = UserSimilarity.new(user, similar_user).score + dissimialar_score = UserSimilarity.new(user, dissimilar_user).score + expect(simialar_score).to be > dissimialar_score + end + + it "Is not affected by stop words" do + user.summary = user.summary + " throughout yourself can indeed otherwise thru yourselves through yours by inc others" + dissimilar_user.summary = dissimilar_user.summary + " throughout yourself can indeed otherwise thru yourselves through yours by inc others" + simialar_score = UserSimilarity.new(user, similar_user).score + dissimialar_score = UserSimilarity.new(user, dissimilar_user).score + expect(simialar_score).to be > dissimialar_score + end + + it "Is affected by non-stop words" do + user.mentee_description = "Hot dogs languages punk rock hello" + dissimilar_user.mentor_description = "Hot dogs languages punk rock hello " + simialar_score = UserSimilarity.new(user, similar_user).score + dissimialar_score = UserSimilarity.new(user, dissimilar_user).score + expect(simialar_score).to be < dissimialar_score + end +end diff --git a/spec/mailers/notify_mailer_spec.rb b/spec/mailers/notify_mailer_spec.rb index 6a9fcb45d..54aba188b 100644 --- a/spec/mailers/notify_mailer_spec.rb +++ b/spec/mailers/notify_mailer_spec.rb @@ -119,6 +119,10 @@ RSpec.describe NotifyMailer, type: :mailer do mentee_email = described_class.mentee_email(mentee, mentor) expect(mentee_email.subject).to eq "You have been matched with a DEV mentor!" end + it "renders proper from" do + mentee_email = described_class.mentee_email(mentee, mentor) + expect(mentee_email.from).to include "liana@dev.to" + end end describe "#mentor_email" do @@ -128,6 +132,10 @@ RSpec.describe NotifyMailer, type: :mailer do mentor_email = described_class.mentor_email(mentor, mentee) expect(mentor_email.subject).to eq "You have been matched with a new DEV mentee!" end + it "renders proper subject" do + mentor_email = described_class.mentor_email(mentor, mentee) + expect(mentor_email.from).to include "liana@dev.to" + end end end end