Get search dictionary based on I18n.default_locale (merge pull request #3856)

Implementation tries to be open for further extensions, such as deciding on
search dictionary based on configuration option or by locale set for
given user.
This commit is contained in:
Paweł Świątkowski
2020-04-12 14:22:36 +02:00
committed by GitHub
parent 426c1c5fd2
commit d99875cde2
8 changed files with 94 additions and 13 deletions

View File

@@ -19,7 +19,8 @@ module SearchCache
end end
def set_tsvector(value, weight) def set_tsvector(value, weight)
"setweight(to_tsvector('spanish', unaccent(coalesce(#{quote(strip_html(value))}, ''))), #{quote(weight)})" dict = quote(SearchDictionarySelector.call)
"setweight(to_tsvector(#{dict}, unaccent(coalesce(#{quote(strip_html(value))}, ''))), #{quote(weight)})"
end end
def quote(value) def quote(value)

View File

@@ -5,14 +5,18 @@ module Searchable
include PgSearch include PgSearch
include SearchCache include SearchCache
pg_search_scope :pg_search, { pg_search_scope :pg_search, ->(query) do
cached_votes_up_present = column_names.include?("cached_votes_up")
{
against: :ignored, # not used since using a tsvector_column against: :ignored, # not used since using a tsvector_column
using: { using: {
tsearch: { tsvector_column: "tsv", dictionary: "spanish", prefix: true } tsearch: { tsvector_column: "tsv", dictionary: SearchDictionarySelector.call, prefix: true }
}, },
ignoring: :accents, ignoring: :accents,
ranked_by: "(:tsearch)", ranked_by: "(:tsearch)",
order_within_rank: (column_names.include?("cached_votes_up") ? "#{table_name}.cached_votes_up DESC" : nil) order_within_rank: (cached_votes_up_present ? "#{table_name}.cached_votes_up DESC" : nil),
query: query
} }
end end
end end
end

View File

@@ -0,0 +1,42 @@
module SearchDictionarySelector
SQL_QUERY = "SELECT cfgname FROM pg_ts_config".freeze
I18N_TO_DICTIONARY = {
en: "english",
de: "german",
fi: "finnish",
fr: "french",
dk: "danish",
nl: "dutch",
hu: "hungarian",
it: "italian",
nn: "norwegian",
nb: "norwegian",
pt: "portuguese",
ro: "romanian",
ru: "russian",
es: "spanish",
sv: "swedish",
tr: "turkish"
}.freeze
class << self
def call
find_from_i18n_default
end
private
def find_from_i18n_default
key_to_lookup = I18n.default_locale.to_s.split("-").first.to_sym
dictionary = I18N_TO_DICTIONARY[key_to_lookup]
dictionary ||= "simple"
available_dictionaries.include?(dictionary) ? dictionary : available_dictionaries.first
end
def available_dictionaries
result = ActiveRecord::Base.connection.execute(SQL_QUERY)
result.to_a.map { |row| row["cfgname"] }
end
end
end

View File

@@ -1410,7 +1410,7 @@ describe "Proposals" do
end end
end end
scenario "Order by relevance by default", :js do scenario "Order by relevance by default", :spanish_search, :js do
create(:proposal, title: "Show you got", cached_votes_up: 10) create(:proposal, title: "Show you got", cached_votes_up: 10)
create(:proposal, title: "Show what you got", cached_votes_up: 1) create(:proposal, title: "Show what you got", cached_votes_up: 1)
create(:proposal, title: "Show you got", cached_votes_up: 100) create(:proposal, title: "Show you got", cached_votes_up: 100)

View File

@@ -0,0 +1,30 @@
require "rails_helper"
describe SearchDictionarySelector do
context "from I18n default locale" do
before { allow(subject).to receive(:call).and_call_original }
around do |example|
original_i18n_default = I18n.default_locale
begin
example.run
ensure
I18n.default_locale = original_i18n_default
end
end
it "returns correct dictionary for simple locale" do
I18n.default_locale = :es
expect(subject.call).to eq("spanish")
end
it "returns correct dictionary for compound locale" do
I18n.default_locale = :"pt-BR"
expect(subject.call).to eq("portuguese")
end
it "returns simple for unsupported locale" do
expect(I18n).to receive(:default_locale).and_return(:pl) # avoiding I18n::InvalidLocale
expect(subject.call).to eq("simple")
end
end
end

View File

@@ -503,7 +503,7 @@ describe Debate do
end end
context "stemming" do context "stemming" do
it "searches word stems" do it "searches word stems in Spanish", :spanish_search do
debate = create(:debate, title: "limpiar") debate = create(:debate, title: "limpiar")
results = Debate.search("limpiará") results = Debate.search("limpiará")

View File

@@ -558,7 +558,7 @@ describe Proposal do
end end
context "case" do context "case" do
it "searches case insensite" do it "searches case insensitive" do
proposal = create(:proposal, title: "SHOUT") proposal = create(:proposal, title: "SHOUT")
results = Proposal.search("shout") results = Proposal.search("shout")

View File

@@ -128,6 +128,10 @@ RSpec.configure do |config|
allow(Time).to receive(:zone).and_return(application_zone) allow(Time).to receive(:zone).and_return(application_zone)
end end
config.before(:each, :spanish_search) do |example|
allow(SearchDictionarySelector).to receive(:call).and_return("spanish")
end
# Allows RSpec to persist some state between runs in order to support # Allows RSpec to persist some state between runs in order to support
# the `--only-failures` and `--next-failure` CLI options. # the `--only-failures` and `--next-failure` CLI options.
config.example_status_persistence_file_path = "spec/examples.txt" config.example_status_persistence_file_path = "spec/examples.txt"