diff --git a/app/models/concerns/search_cache.rb b/app/models/concerns/search_cache.rb index 77dbabb73..ad25b07de 100644 --- a/app/models/concerns/search_cache.rb +++ b/app/models/concerns/search_cache.rb @@ -19,7 +19,8 @@ module SearchCache end 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 def quote(value) diff --git a/app/models/concerns/searchable.rb b/app/models/concerns/searchable.rb index 8cdbafb46..f6e5a8c21 100644 --- a/app/models/concerns/searchable.rb +++ b/app/models/concerns/searchable.rb @@ -5,14 +5,18 @@ module Searchable include PgSearch include SearchCache - pg_search_scope :pg_search, { - against: :ignored, # not used since using a tsvector_column - using: { - tsearch: { tsvector_column: "tsv", dictionary: "spanish", prefix: true } - }, - ignoring: :accents, - ranked_by: "(:tsearch)", - order_within_rank: (column_names.include?("cached_votes_up") ? "#{table_name}.cached_votes_up DESC" : nil) - } + 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 + using: { + tsearch: { tsvector_column: "tsv", dictionary: SearchDictionarySelector.call, prefix: true } + }, + ignoring: :accents, + ranked_by: "(:tsearch)", + order_within_rank: (cached_votes_up_present ? "#{table_name}.cached_votes_up DESC" : nil), + query: query + } + end end end diff --git a/lib/search_dictionary_selector.rb b/lib/search_dictionary_selector.rb new file mode 100644 index 000000000..dca13b8c6 --- /dev/null +++ b/lib/search_dictionary_selector.rb @@ -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 diff --git a/spec/features/proposals_spec.rb b/spec/features/proposals_spec.rb index 2ea2757bd..5e836e0b2 100644 --- a/spec/features/proposals_spec.rb +++ b/spec/features/proposals_spec.rb @@ -1410,7 +1410,7 @@ describe "Proposals" do 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 what you got", cached_votes_up: 1) create(:proposal, title: "Show you got", cached_votes_up: 100) diff --git a/spec/lib/search_dictionary_selector_spec.rb b/spec/lib/search_dictionary_selector_spec.rb new file mode 100644 index 000000000..d9a618b25 --- /dev/null +++ b/spec/lib/search_dictionary_selector_spec.rb @@ -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 diff --git a/spec/models/debate_spec.rb b/spec/models/debate_spec.rb index ab9c2e65e..8bda98ba6 100644 --- a/spec/models/debate_spec.rb +++ b/spec/models/debate_spec.rb @@ -503,7 +503,7 @@ describe Debate do end context "stemming" do - it "searches word stems" do + it "searches word stems in Spanish", :spanish_search do debate = create(:debate, title: "limpiar") results = Debate.search("limpiará") diff --git a/spec/models/proposal_spec.rb b/spec/models/proposal_spec.rb index d30e28bfd..b92d4726c 100644 --- a/spec/models/proposal_spec.rb +++ b/spec/models/proposal_spec.rb @@ -558,7 +558,7 @@ describe Proposal do end context "case" do - it "searches case insensite" do + it "searches case insensitive" do proposal = create(:proposal, title: "SHOUT") results = Proposal.search("shout") diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 055109bf5..ccacaa333 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -128,6 +128,10 @@ RSpec.configure do |config| allow(Time).to receive(:zone).and_return(application_zone) 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 # the `--only-failures` and `--next-failure` CLI options. config.example_status_persistence_file_path = "spec/examples.txt"