Add point weights for tag follows (#1229)

* Add point weights for tag follows

* Adjust num articles to initially show up on home page

* Fix schema.rb

* Adjust follow policy spec

* Adjust dashboard styling for follow points form
This commit is contained in:
Ben Halpern 2018-11-30 15:36:58 -05:00 committed by GitHub
parent b51042f035
commit 651ab3ce35
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 191 additions and 15 deletions

View file

@ -31,7 +31,14 @@ function insertNewArticles(user){
var articlePoints = 0
var containsUserID = findOne([article.user_id], user.followed_user_ids || [])
var containsOrganizationID = findOne([article.organization_id], user.followed_organization_ids || [])
articlePoints = articlePoints + (intersect_arrays(user.followed_tag_names, article.cached_tag_list_array).length*2) + article.positive_reactions_count
var intersectedTags = intersect_arrays(user.followed_tag_names, article.cached_tag_list_array)
var followedPoints = 1
JSON.parse(user.followed_tags).map(function(tag) {
if (intersectedTags.includes(tag.name)) {
followedPoints = followedPoints + tag.points
}
})
articlePoints = articlePoints + (followedPoints*2) + article.positive_reactions_count
if (containsUserID || article.user_id === user.id) {
articlePoints = articlePoints + 16
}
@ -61,7 +68,14 @@ function insertTopArticles(user){
var articlePoints = 0
var containsUserID = findOne([article.user_id], user.followed_user_ids || [])
var containsOrganizationID = findOne([article.organization_id], user.followed_organization_ids || [])
articlePoints = articlePoints + intersect_arrays(user.followed_tag_names, article.cached_tag_list_array).length
var intersectedTags = intersect_arrays(user.followed_tag_names, article.cached_tag_list_array)
var followedPoints = 1
JSON.parse(user.followed_tags).map(function(tag) {
if (intersectedTags.includes(tag.name)) {
followedPoints = followedPoints + tag.points
}
})
articlePoints = articlePoints + followedPoints
if (containsUserID) {
articlePoints = articlePoints + 1
}

View file

@ -164,7 +164,7 @@
}
h2{
font-weight:500;
margin-bottom:5px;
margin-bottom:10px;
margin-top:8px;
font-size:22px;
@media screen and ( min-width: 800px ) {
@ -182,6 +182,30 @@
font-size: 0.6em;
vertical-align:0.16em;
}
form {
background: $light-gray;
padding: 8px 15px;
margin-bottom: 10px;
border-radius: 3px;
@media screen and ( min-width: 750px ) {
display: inline-block;
float: right;
}
input {
font-size: 16px;
border-radius: 3px;
padding: 3px 8px;
width: 50px;
border: 1px solid $black;
font-weight: bold;
&[type="submit"] {
width: auto;
background: $green;
font-family: $helvetica-condensed;
margin-left: 5px;
}
}
}
}
h4{
margin-top:0px;

View file

@ -38,7 +38,7 @@ class AsyncInfoController < ApplicationController
username: @user.username,
profile_image_90: ProfileImage.new(@user).get(90),
followed_tag_names: @user.cached_followed_tag_names,
followed_tags: @user.cached_followed_tags.to_json(only: %i[id name bg_color_hex text_color_hex]),
followed_tags: @user.cached_followed_tags.to_json(only: %i[id name bg_color_hex text_color_hex], methods: [:points]),
followed_user_ids: @user.cached_following_users_ids,
followed_organization_ids: @user.cached_following_organizations_ids,
reading_list_ids: ReadingList.new(@user).cached_ids_of_articles,

View file

@ -10,9 +10,11 @@ class DashboardsController < ApplicationController
current_user
end
authorize (@user || User), :dashboard_show?
if params[:which] == "following_users"
if params[:which] == "following" || params[:which] == "following_users"
@follows = @user.follows_by_type("User").
order("created_at DESC").includes(:followable).limit(80)
@followed_tags = @user.follows_by_type("ActsAsTaggableOn::Tag").
order("points DESC").includes(:followable).limit(80)
elsif params[:which] == "user_followers"
@follows = Follow.where(followable_id: @user.id, followable_type: "User").
includes(:follower).order("created_at DESC").limit(80)

View file

@ -37,4 +37,18 @@ class FollowsController < ApplicationController
current_user.touch
render json: { outcome: @result }
end
def update
@follow = Follow.find(params[:id])
authorize @follow
if @follow.update(follow_params)
redirect_to "/dashboard/following"
end
end
private
def follow_params
params.require(:follow).permit(policy(Follow).permitted_attributes)
end
end

View file

@ -107,7 +107,7 @@ class StoriesController < ApplicationController
def handle_base_index
@home_page = true
@page = (params[:page] || 1).to_i
num_articles = 15
num_articles = 25
@stories = article_finder(num_articles)
add_param_context(:page, :timeframe)

View file

@ -2,8 +2,14 @@ class UserDecorator < ApplicationDecorator
delegate_all
def cached_followed_tags
Rails.cache.fetch("user-#{id}-#{updated_at}/followed_tags", expires_in: 20.hours) do
Tag.where(id: Follow.where(follower_id: id, followable_type: "ActsAsTaggableOn::Tag").pluck(:followable_id)).order("hotness_score DESC")
Rails.cache.fetch("user-#{id}-#{updated_at}/followed_tags_11-30", expires_in: 20.hours) do
follows_query = Follow.where(follower_id: id, followable_type: "ActsAsTaggableOn::Tag").pluck(:followable_id, :points)
tags = Tag.where(id: follows_query.map { |f| f[0] }).order("hotness_score DESC")
tags.each do |t|
follow_query_item = follows_query.detect{|f| f[0] == t.id}
t.points = follow_query_item[1]
end
tags
end
end

View file

@ -1,4 +1,7 @@
class Tag < ActsAsTaggableOn::Tag
attr_accessor :points
include AlgoliaSearch
acts_as_followable
resourcify

View file

@ -2,4 +2,18 @@ class FollowPolicy < ApplicationPolicy
def create?
!user_is_banned?
end
def update?
user_is_follower?
end
def permitted_attributes
%i[points]
end
private
def user_is_follower?
record.follower_id == user.id && record.follower_type == "User"
end
end

View file

@ -10,9 +10,9 @@
<span>FOLLOWERS</span>
<span>(<%= @user.followers_count %>)</span>
</a>
<a class="action <%= 'active' if params[:which] == "following_users" %>" href="/dashboard/following_users">
<a class="action <%= 'active' if params[:which].to_s.include?("following") %>" href="/dashboard/following">
<span>FOLLOWING</span>
<span>(<%= @user.following_users_count %>)</span>
<span>(<%= @user.following_users_count + @user.following_tags_count %>)</span>
</a>
</div>
<% if @user.org_admin && @user.organization && (params[:which].blank? || params[:which] == "organization") %>
@ -32,7 +32,31 @@
<%= render "dashboard_article", article: article, org_admin: true %>
<% end %>
</div>
<% elsif params[:which] == "user_followers" || params[:which] == "following_users" || params[:which] == "organization_user_followers" %>
<% elsif params[:which] == "user_followers" || params[:which] == "following_users" || params[:which] == "following" || params[:which] == "organization_user_followers" %>
<% if @followed_tags %>
<h2>Followed tags (<%= @user.following_tags_count %>)</h2>
<p><em>Adjust <strong>Follow Weight</strong> to make a tag show up less or more in your feed (default 1.0)</em></p>
<% @followed_tags.each do |follow| %>
<% tag = follow.followable %>
<% if tag %>
<% color = HexComparer.new([tag.bg_color_hex || "#0000000",tag.text_color_hex || "#ffffff"]).brightness(0.8) %>
<div class="single-article" style="border-color:<%= color %>;box-shadow: 3px 3px 0px <%= color %>">
<h2>
<a href="/t/<%= tag.name %>">
<%= tag.name %>
</a>
<%= form_for(follow) do |f| %>
<%= f.label(:points, "Follow Weight:") %>
<%= f.text_field(:points) %>
<%= f.submit "Submit" %>
<% end %>
</h2>
</a>
</div>
<% end %>
<% end %>
<h2>Followed users (<%= @user.following_users_count %>)</h2>
<% end %>
<% @follows.each do |follow| %>
<% user = params[:which].include?("followers") ? follow.follower : follow.followable %>
<% if user %>

View file

@ -102,7 +102,7 @@ Rails.application.routes.draw do
get "/reports/:slug", to: "feedback_messages#show"
resources :organizations, only: %i[update create]
resources :followed_articles, only: [:index]
resources :follows, only: %i[show create]
resources :follows, only: %i[show create update]
resources :giveaways, only: %i[create update]
resources :image_uploads, only: [:create]
resources :blocks
@ -238,7 +238,7 @@ Rails.application.routes.draw do
get "/dashboard" => "dashboards#show"
get "/dashboard/:which" => "dashboards#show",
constraints: {
which: /organization|organization_user_followers|user_followers|following_users|reading/
which: /organization|organization_user_followers|user_followers|following_users|following|reading/
}
get "/dashboard/:username" => "dashboards#show"

View file

@ -0,0 +1,5 @@
class AddPointsToFollows < ActiveRecord::Migration[5.1]
def change
add_column :follows, :points, :float, default: 1.0
end
end

View file

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20181127173004) do
ActiveRecord::Schema.define(version: 20181129222416) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -323,6 +323,7 @@ ActiveRecord::Schema.define(version: 20181127173004) do
t.string "followable_type", null: false
t.integer "follower_id", null: false
t.string "follower_type", null: false
t.float "points", default: 1.0
t.datetime "updated_at"
t.index ["followable_id", "followable_type"], name: "fk_followables"
t.index ["follower_id", "follower_type"], name: "fk_follows"
@ -440,7 +441,7 @@ ActiveRecord::Schema.define(version: 20181127173004) do
t.index ["chat_channel_id"], name: "index_messages_on_chat_channel_id"
t.index ["user_id"], name: "index_messages_on_user_id"
end
create_table "notes", id: :serial, force: :cascade do |t|
t.integer "author_id"
t.text "content"

View file

@ -351,6 +351,39 @@ RSpec.describe User, type: :model do
end
end
describe "#cached_followed_tags" do
let(:tag1) { create(:tag) }
let(:tag2) { create(:tag) }
let(:tag3) { create(:tag) }
let(:tag4) { create(:tag) }
it "returns empty if no tags followed" do
expect(user.decorate.cached_followed_tags.size).to eq(0)
end
it "returns array of tags if user follows them" do
user.follow(tag1)
user.follow(tag2)
user.follow(tag3)
expect(user.decorate.cached_followed_tags.size).to eq(3)
end
it "returns tag object with name" do
user.follow(tag1)
expect(user.decorate.cached_followed_tags.first.name).to eq(tag1.name)
end
it "returns follow points for tag" do
user.follow(tag1)
expect(user.decorate.cached_followed_tags.first.points).to eq(1.0)
end
it "returns adjusted points for tag" do
user.follow(tag1)
Follow.last.update(points: 0.1)
expect(user.decorate.cached_followed_tags.first.points).to eq(0.1)
end
end
it "inserts into mailchimp" do
expect(user.subscribe_to_mailchimp_newsletter_without_delay).to eq true
end

View file

@ -70,6 +70,14 @@ RSpec.describe "Dashboards", type: :request do
get "/dashboard/following_users"
expect(response.body).to include CGI.escapeHTML(second_user.name)
end
it "renders the current user's tag followings" do
user.follow second_user
tag = create(:tag)
user.follow tag
login_as user
get "/dashboard/following"
expect(response.body).to include CGI.escapeHTML(tag.name)
end
end
end

View file

@ -0,0 +1,28 @@
require "rails_helper"
RSpec.describe "Following/Unfollowing", type: :request do
let(:user) { create(:user) }
let(:user_2) { create(:user) }
let(:tag) { create(:tag) }
before do
login_as user
end
describe "PUT follows/:id" do
it "updates user to offer mentorship" do
user.follow(tag)
put "/follows/#{Follow.last.id}",
params: { follow: { points: 3.0 } }
expect(Follow.last.points).to eq(3.0)
end
it "does not update if follow does not belong to user" do
user_2.follow(tag)
expect do
put "/follows/#{Follow.last.id}",
params: { follow: { points: 3.0 } }
end.to raise_error(Pundit::NotAuthorizedError)
end
end
end