Merge pull request #3407 from evazion/feat-tag-autocorrect
Fix #3406: Autocorrect typos during autocomplete
This commit is contained in:
@@ -244,7 +244,7 @@
|
|||||||
$.ajax({
|
$.ajax({
|
||||||
url: "/tags/autocomplete.json",
|
url: "/tags/autocomplete.json",
|
||||||
data: {
|
data: {
|
||||||
"search[name_matches]": term + "*"
|
"search[name_matches]": term
|
||||||
},
|
},
|
||||||
method: "get",
|
method: "get",
|
||||||
success: function(data) {
|
success: function(data) {
|
||||||
|
|||||||
@@ -817,6 +817,18 @@ class Tag < ApplicationRecord
|
|||||||
where("tags.post_count > 0")
|
where("tags.post_count > 0")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# ref: https://www.postgresql.org/docs/current/static/pgtrgm.html#idm46428634524336
|
||||||
|
def order_similarity(name)
|
||||||
|
# trunc(3 * sim) reduces the similarity score from a range of 0.0 -> 1.0 to just 0, 1, or 2.
|
||||||
|
# This groups tags first by approximate similarity, then by largest tags within groups of similar tags.
|
||||||
|
order("trunc(3 * similarity(name, #{sanitize(name)})) DESC", "post_count DESC", "name DESC")
|
||||||
|
end
|
||||||
|
|
||||||
|
# ref: https://www.postgresql.org/docs/current/static/pgtrgm.html#idm46428634524336
|
||||||
|
def fuzzy_name_matches(name)
|
||||||
|
where("tags.name % ?", name)
|
||||||
|
end
|
||||||
|
|
||||||
def name_matches(name)
|
def name_matches(name)
|
||||||
where("tags.name LIKE ? ESCAPE E'\\\\'", normalize_name(name).to_escaped_for_sql_like)
|
where("tags.name LIKE ? ESCAPE E'\\\\'", normalize_name(name).to_escaped_for_sql_like)
|
||||||
end
|
end
|
||||||
@@ -829,6 +841,10 @@ class Tag < ApplicationRecord
|
|||||||
q = where("true")
|
q = where("true")
|
||||||
params = {} if params.blank?
|
params = {} if params.blank?
|
||||||
|
|
||||||
|
if params[:fuzzy_name_matches].present?
|
||||||
|
q = q.fuzzy_name_matches(params[:fuzzy_name_matches])
|
||||||
|
end
|
||||||
|
|
||||||
if params[:name_matches].present?
|
if params[:name_matches].present?
|
||||||
q = q.name_matches(params[:name_matches])
|
q = q.name_matches(params[:name_matches])
|
||||||
end
|
end
|
||||||
@@ -865,6 +881,8 @@ class Tag < ApplicationRecord
|
|||||||
q = q.reorder("id desc")
|
q = q.reorder("id desc")
|
||||||
when "count"
|
when "count"
|
||||||
q = q.reorder("post_count desc")
|
q = q.reorder("post_count desc")
|
||||||
|
when "similarity"
|
||||||
|
q = q.order_similarity(params[:fuzzy_name_matches]) if params[:fuzzy_name_matches].present?
|
||||||
else
|
else
|
||||||
q = q.reorder("id desc")
|
q = q.reorder("id desc")
|
||||||
end
|
end
|
||||||
@@ -873,21 +891,29 @@ class Tag < ApplicationRecord
|
|||||||
end
|
end
|
||||||
|
|
||||||
def names_matches_with_aliases(name)
|
def names_matches_with_aliases(name)
|
||||||
query1 = Tag.select("tags.name, tags.post_count, tags.category, null AS antecedent_name")
|
name = normalize_name(name)
|
||||||
.search(:name_matches => name, :order => "count").limit(10)
|
wildcard_name = name + '*'
|
||||||
|
|
||||||
|
query1 = Tag.select("tags.name, tags.post_count, tags.category, null AS antecedent_name")
|
||||||
|
.search(:name_matches => wildcard_name, :order => "count").limit(10)
|
||||||
|
|
||||||
name = name.mb_chars.downcase.to_escaped_for_sql_like
|
|
||||||
query2 = TagAlias.select("tags.name, tags.post_count, tags.category, tag_aliases.antecedent_name")
|
query2 = TagAlias.select("tags.name, tags.post_count, tags.category, tag_aliases.antecedent_name")
|
||||||
.joins("INNER JOIN tags ON tags.name = tag_aliases.consequent_name")
|
.joins("INNER JOIN tags ON tags.name = tag_aliases.consequent_name")
|
||||||
.where("tag_aliases.antecedent_name LIKE ? ESCAPE E'\\\\'", name)
|
.where("tag_aliases.antecedent_name LIKE ? ESCAPE E'\\\\'", wildcard_name.to_escaped_for_sql_like)
|
||||||
.active
|
.active
|
||||||
.where("tags.name NOT LIKE ? ESCAPE E'\\\\'", name)
|
.where("tags.name NOT LIKE ? ESCAPE E'\\\\'", wildcard_name.to_escaped_for_sql_like)
|
||||||
.where("tag_aliases.post_count > 0")
|
.where("tag_aliases.post_count > 0")
|
||||||
.order("tag_aliases.post_count desc")
|
.order("tag_aliases.post_count desc")
|
||||||
.limit(20) # Get 20 records even though only 10 will be displayed in case some duplicates get filtered out.
|
.limit(20) # Get 20 records even though only 10 will be displayed in case some duplicates get filtered out.
|
||||||
|
|
||||||
sql_query = "((#{query1.to_sql}) UNION ALL (#{query2.to_sql})) AS unioned_query"
|
sql_query = "((#{query1.to_sql}) UNION ALL (#{query2.to_sql})) AS unioned_query"
|
||||||
Tag.select("DISTINCT ON (name, post_count) *").from(sql_query).order("post_count desc").limit(10)
|
tags = Tag.select("DISTINCT ON (name, post_count) *").from(sql_query).order("post_count desc").limit(10)
|
||||||
|
|
||||||
|
if tags.empty?
|
||||||
|
tags = Tag.fuzzy_name_matches(name).order_similarity(name).nonempty.limit(10)
|
||||||
|
end
|
||||||
|
|
||||||
|
tags
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
9
db/migrate/20171127195124_add_trigram_index_to_tags.rb
Normal file
9
db/migrate/20171127195124_add_trigram_index_to_tags.rb
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
class AddTrigramIndexToTags < ActiveRecord::Migration
|
||||||
|
def up
|
||||||
|
execute "create index index_tags_on_name_trgm on tags using gin (name gin_trgm_ops)"
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
execute "drop index index_tags_on_name_trgm"
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -7008,6 +7008,13 @@ CREATE UNIQUE INDEX index_tags_on_name ON tags USING btree (name);
|
|||||||
CREATE INDEX index_tags_on_name_pattern ON tags USING btree (name text_pattern_ops);
|
CREATE INDEX index_tags_on_name_pattern ON tags USING btree (name text_pattern_ops);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: index_tags_on_name_trgm; Type: INDEX; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE INDEX index_tags_on_name_trgm ON tags USING gin (name gin_trgm_ops);
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: index_token_buckets_on_user_id; Type: INDEX; Schema: public; Owner: -
|
-- Name: index_token_buckets_on_user_id; Type: INDEX; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
@@ -7521,3 +7528,5 @@ INSERT INTO schema_migrations (version) VALUES ('20170914200122');
|
|||||||
|
|
||||||
INSERT INTO schema_migrations (version) VALUES ('20171106075030');
|
INSERT INTO schema_migrations (version) VALUES ('20171106075030');
|
||||||
|
|
||||||
|
INSERT INTO schema_migrations (version) VALUES ('20171127195124');
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user