diff --git a/app/models/message/searchable.rb b/app/models/message/searchable.rb index 58ea47f..b792cd0 100644 --- a/app/models/message/searchable.rb +++ b/app/models/message/searchable.rb @@ -6,7 +6,15 @@ module Message::Searchable after_update_commit :update_in_index after_destroy_commit :remove_from_index - scope :search, ->(query) { joins("join message_search_index idx on messages.id = idx.rowid").where("idx.body match ?", query).ordered } + scope :search, ->(query) { joins("join message_search_index idx on messages.id = idx.rowid").where("idx.body match ?", match_terms(query)).ordered } + end + + class_methods do + # Quotes each word, so that FTS5 searches for AND, OR, NOT and NEAR rather than + # parsing them as operators, which fails on a query like "AND" or "salt AND". + def match_terms(query) + query.split.map { |word| %("#{word.gsub('"', '""')}") }.join(" ") + end end private diff --git a/test/controllers/searches_controller_test.rb b/test/controllers/searches_controller_test.rb index 80abfb4..34ff01b 100644 --- a/test/controllers/searches_controller_test.rb +++ b/test/controllers/searches_controller_test.rb @@ -20,6 +20,24 @@ class SearchesControllerTest < ActionDispatch::IntegrationTest assert_select ".message", text: /Hello world!/ end + test "operator words are searched for as words" do + rooms(:designers).messages.create! body: "Salt and pepper", client_message_id: "operators", creator: users(:david) + + get searches_url, params: { q: "salt AND" } + + assert_response :success + assert_select ".message", text: /Salt and pepper/ + end + + test "searching for an operator word alone doesn't fail" do + %w[ AND OR NOT ].each do |word| + get searches_url, params: { q: word } + + assert_response :success + assert_select ".message", count: 0 + end + end + test "unreachable messages are not found" do memberships(:david_designers).destroy! diff --git a/test/models/message_test.rb b/test/models/message_test.rb index 5ffecea..e6add9a 100644 --- a/test/models/message_test.rb +++ b/test/models/message_test.rb @@ -65,6 +65,12 @@ class MessageTest < ActiveSupport::TestCase assert_equal room.messages.ordered.first(Message::Pagination::PAGE_SIZE).map(&:id), room.messages.first_page.map(&:id) end + test "search reads quotes and operator words in the query as text" do + message = rooms(:designers).messages.create!(body: "Say hi, NOT bye", client_message_id: "quoted", creator: users(:david)) + + assert_equal [ message ], Message.search(%(say "hi NOT)).to_a + end + private def create_new_message_in(room) room.messages.create!(creator: users(:jason), body: "Hello", client_message_id: "123")