start on automated story title and tagging suggestions

Rather than keep "poorly titled" and "poorly tagged" as reasons for
flagging, make the user do the work of suggesting new ones.

At some point, suggested taggings will flip to real taggings once
they reach a certain count (to be determined later).  This also has
to take into account tagging sets that don't contain current tags,
for when they need to be removed.

For titles, I'm not yet sure how to handle this in an automated
fashion except for the (probably rare) case of multiple users
submitting the same exact thing, but at least collect them for now.

Issue #207
This commit is contained in:
joshua stein 2015-10-14 20:32:24 -05:00
parent 700c63b936
commit e940601a2f
16 changed files with 239 additions and 93 deletions

View file

@ -823,7 +823,7 @@ div#story_box #story_tags_a {
div#story_box textarea {
width: 600px;
}
div#story_box div.markdown_help_toggler {
div#story_box div.actions {
margin-left: 7em;
width: 610px;
}

View file

@ -222,7 +222,7 @@
div#story_box button,
div#story_box textarea,
div#story_box #story_tags_a,
div.markdown_help_toggler {
div.actions {
margin: 0 !important;
width: 100% !important;
}

View file

@ -1,12 +1,11 @@
class StoriesController < ApplicationController
before_filter :require_logged_in_user_or_400,
:only => [ :upvote, :downvote, :unvote, :hide, :unhide, :preview ]
before_filter :require_logged_in_user, :only => [ :destroy, :create, :edit,
:fetch_url_title, :new ]
:fetch_url_title, :new, :suggest ]
before_filter :find_user_story, :only => [ :destroy, :edit, :undelete,
:update ]
before_filter :find_story!, :only => [ :suggest, :submit_suggestions ]
def create
@title = "Submit Story"
@ -169,6 +168,33 @@ class StoriesController < ApplicationController
end
end
def suggest
if (st = @story.suggested_taggings.where(:user_id => @user.id)).any?
@story.tags_a = st.map{|st| st.tag.tag }
end
if tt = @story.suggested_titles.where(:user_id => @user.id).first
@story.title = tt.title
end
end
def submit_suggestions
ostory = @story.dup
@story.title = params[:story][:title]
if @story.valid?
if @story.title != ostory.title
@story.save_suggested_title_for_user!(@story.title, @user)
end
if @story.tags_a.sort != params[:story][:tags_a].sort
@story.save_suggested_tags_a_for_user!(params[:story][:tags_a], @user)
end
flash[:success] = "Your suggested changes have been noted."
redirect_to ostory.comments_path
else
render :action => "suggest"
end
end
def undelete
if !(@story.is_editable_by_user?(@user) &&
@story.is_undeletable_by_user?(@user))
@ -291,6 +317,13 @@ private
story
end
def find_story!
@story = find_story
if !@story
raise ActiveRecord::RecordNotFound
end
end
def find_user_story
if @user.is_moderator?
@story = Story.where(:short_id => params[:story_id] || params[:id]).first

View file

@ -8,6 +8,8 @@ class Story < ActiveRecord::Base
:foreign_key => "merged_story_id"
has_many :taggings,
:autosave => true
has_many :suggested_taggings
has_many :suggested_titles
has_many :comments,
:inverse_of => :story
has_many :tags, :through => :taggings
@ -478,7 +480,8 @@ class Story < ActiveRecord::Base
@_tags_a = []
def tags_a
@_tags_a ||= self.taggings.map{|t| t.tag.tag }
@_tags_a ||= self.taggings.reject{|t| t.marked_for_destruction?
}.map{|t| t.tag.tag }
end
def tags_a=(new_tag_names_a)
@ -501,6 +504,47 @@ class Story < ActiveRecord::Base
end
end
def save_suggested_tags_a_for_user!(new_tag_names_a, user)
st = self.suggested_taggings.where(:user_id => user.id)
st.each do |tagging|
if !new_tag_names_a.include?(tagging.tag.tag)
tagging.destroy
end
end
st.reload
new_tag_names_a.each do |tag_name|
# XXX: AR bug? st.exists?(:tag => tag_name) does not work
if tag_name.to_s != "" && !st.map{|x| x.tag.tag }.include?(tag_name)
if (t = Tag.active.where(:tag => tag_name).first) &&
t.valid_for?(user)
tg = self.suggested_taggings.build
tg.user_id = user.id
tg.tag_id = t.id
tg.save!
st.reload
else
next
end
end
end
# TODO: promote suggested tags to real one when count reaches something
end
def save_suggested_title_for_user!(title, user)
st = self.suggested_titles.where(:user_id => user.id).first
if !st
st = self.suggested_titles.build
st.user_id = user.id
end
st.title = title
st.save!
end
def title=(t)
# change unicode whitespace characters into real spaces
self[:title] = t.strip

View file

@ -0,0 +1,5 @@
class SuggestedTagging < ActiveRecord::Base
belongs_to :tag
belongs_to :story
belongs_to :user
end

View file

@ -0,0 +1,4 @@
class SuggestedTitle < ActiveRecord::Base
belongs_to :story
belongs_to :user
end

View file

@ -15,8 +15,6 @@ class Vote < ActiveRecord::Base
STORY_REASONS = {
"O" => "Off-topic",
"A" => "Already Posted",
"T" => "Poorly Tagged",
"L" => "Poorly Titled",
"S" => "Spam",
"" => "Cancel",
}

View file

@ -25,6 +25,7 @@
<% end %>
<div class="box">
<% unless defined?(suggesting) %>
<div class="boxline">
<% if f.object.url_is_editable_by_user?(@user) %>
<%= f.label :url, "URL:", :class => "required" %>
@ -38,6 +39,7 @@
</div>
<% end %>
</div>
<% end %>
<div class="boxline">
<%= f.label :title, "Title:", :class => "required" %>
@ -63,6 +65,7 @@
f.object.tags_a), {}, { :multiple => true } %>
</div>
<% unless defined?(suggesting) %>
<div class="boxline">
<%= f.label :description, "Text:", :class => "required" %>
<%= f.text_area :description, :rows => 15,
@ -70,7 +73,7 @@
:autocomplete => "off" %>
</div>
<div class="boxline markdown_help_toggler">
<div class="boxline actions markdown_help_toggler">
<a href="#" id="story_guidelines_toggler">
Story submission guidelines
</a>
@ -120,7 +123,9 @@
</ul>
</div>
</div>
<% end %>
</div>
<% unless defined?(suggesting) %>
<div class="box">
<div class="boxline">
<%= f.label :user_is_author, "Author:", :class => "required" %>
@ -140,3 +145,4 @@
});
});
</script>
<% end %>

View file

@ -127,6 +127,9 @@ class="story <%= story.vote && story.vote[:vote] == 1 ? "upvoted" : "" %>
:confirm => "Are you sure you want to delete this story?" } %>
<% end %>
<% end %>
<% elsif @user %>
| <%= link_to "suggest", story_suggest_path(story.short_id),
:class => "suggester" %>
<% end %>
<% if !story.is_gone? && @user %>
<% if @user && story.vote && story.vote[:vote] == -1 %>

View file

@ -38,7 +38,7 @@
<p></p>
<div class="box">
<div class="boxline markdown_help_toggler">
<div class="boxline actions markdown_help_toggler">
<div class="markdown_help_label">
Markdown formatting available
</div>

View file

@ -11,7 +11,7 @@
<p></p>
<div class="box">
<div class="boxline markdown_help_toggler">
<div class="boxline actions markdown_help_toggler">
<div class="markdown_help_label">
Markdown formatting available
</div>

View file

@ -0,0 +1,21 @@
<div class="box" id="story_box">
<div class="legend">
Suggest Story Changes
</div>
<%= form_for @story, :url => story_suggest_path(@story.short_id),
:method => :post, :html => { :id => "edit_story" } do |f| %>
<%= render :partial => "stories/form", :locals => { :story => @story,
:f => f, :suggesting => true } %>
<p></p>
<div class="box">
<div class="boxline actions">
<%= submit_tag "Suggest Changes" %>
&nbsp;or <a href="<%= story_path(@story.short_id) %>">cancel</a>
</div>
</div>
<% end %>
</div>

View file

@ -57,6 +57,8 @@ Lobsters::Application.routes.draw do
post "undelete"
post "hide"
post "unhide"
get "suggest"
post "suggest", :action => "submit_suggestions"
end
post "/stories/fetch_url_attributes", :format => "json"
post "/stories/preview" => "stories#preview"

View file

@ -0,0 +1,9 @@
class AddSuggestedTaggings < ActiveRecord::Migration
def change
create_table :suggested_taggings do |t|
t.integer :story_id
t.integer :tag_id
t.integer :user_id
end
end
end

View file

@ -0,0 +1,9 @@
class AddSuggestedTitles < ActiveRecord::Migration
def change
create_table "suggested_titles" do |t|
t.integer :story_id
t.integer :user_id
t.string :title, :limit => 150, :null => false
end
end
end

View file

@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20150730225352) do
ActiveRecord::Schema.define(version: 20151015011231) do
create_table "comments", force: true do |t|
t.datetime "created_at", null: false
@ -133,6 +133,18 @@ ActiveRecord::Schema.define(version: 20150730225352) do
add_index "stories", ["twitter_id"], name: "index_stories_on_twitter_id", using: :btree
add_index "stories", ["url"], name: "url", length: {"url"=>191}, using: :btree
create_table "suggested_taggings", force: true do |t|
t.integer "story_id"
t.integer "tag_id"
t.integer "user_id"
end
create_table "suggested_titles", force: true do |t|
t.integer "story_id"
t.integer "user_id"
t.string "title", limit: 150, null: false
end
create_table "tag_filters", force: true do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false