Skip to content
Snippets Groups Projects
Commit 004672aa authored by unarist's avatar unarist Committed by Eugen Rochko
Browse files

Fix tag search order and not to use tsvector (#3611)

* Sort results by the name
* Switch search method to simple `LIKE` matching instead of tsvector/tsquery

Previously we used scores from ts_rank_cd() to sort results, but it didn't work
because the function returns same score for all results. It's not for calculate
similarity of single words. Sometimes this bug even push out exact matching tag
from results.

Additionally, PostgreSQL supports prefix searching with standard btree index.
Using it offers simpler code, but also less index size and some speed.
parent ad4a28f4
No related branches found
No related tags found
No related merge requests found
...@@ -21,22 +21,9 @@ class Tag < ApplicationRecord ...@@ -21,22 +21,9 @@ class Tag < ApplicationRecord
end end
class << self class << self
def search_for(terms, limit = 5) def search_for(term, limit = 5)
terms = Arel.sql(connection.quote(terms.gsub(/['?\\:]/, ' '))) pattern = sanitize_sql_like(term) + '%'
textsearch = 'to_tsvector(\'simple\', tags.name)' Tag.where('name like ?', pattern).order(:name).limit(limit)
query = 'to_tsquery(\'simple\', \'\'\' \' || ' + terms + ' || \' \'\'\' || \':*\')'
sql = <<-SQL.squish
SELECT
tags.*,
ts_rank_cd(#{textsearch}, #{query}) AS rank
FROM tags
WHERE #{query} @@ #{textsearch}
ORDER BY rank DESC
LIMIT ?
SQL
Tag.find_by_sql([sql, limit])
end end
end end
end end
class ChangeTagSearchIndexToBtree < ActiveRecord::Migration[5.1]
def up
remove_index :tags, name: :hashtag_search_index
execute 'CREATE INDEX hashtag_search_index ON tags (name text_pattern_ops);'
end
def down
remove_index :tags, name: :hashtag_search_index
execute 'CREATE INDEX hashtag_search_index ON tags USING gin(to_tsvector(\'simple\', tags.name));'
end
end
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20170604144747) do ActiveRecord::Schema.define(version: 20170606113804) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -320,7 +320,7 @@ ActiveRecord::Schema.define(version: 20170604144747) do ...@@ -320,7 +320,7 @@ ActiveRecord::Schema.define(version: 20170604144747) do
t.string "name", default: "", null: false t.string "name", default: "", null: false
t.datetime "created_at", null: false t.datetime "created_at", null: false
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
t.index "to_tsvector('simple'::regconfig, (name)::text)", name: "hashtag_search_index", using: :gin t.index "name text_pattern_ops", name: "hashtag_search_index"
t.index ["name"], name: "index_tags_on_name", unique: true t.index ["name"], name: "index_tags_on_name", unique: true
end end
......
...@@ -22,5 +22,14 @@ RSpec.describe Tag, type: :model do ...@@ -22,5 +22,14 @@ RSpec.describe Tag, type: :model do
expect(results).to eq [tag] expect(results).to eq [tag]
end end
it 'finds the exact matching tag as the first item' do
similar_tag = Fabricate(:tag, name: "matchlater")
tag = Fabricate(:tag, name: "match")
results = Tag.search_for("match")
expect(results).to eq [tag, similar_tag]
end
end end
end end
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment