diff --git a/Gemfile b/Gemfile index 552a6fd5..b66cc87d 100644 --- a/Gemfile +++ b/Gemfile @@ -119,3 +119,4 @@ gem "mail", "~> 2.7.1" gem "prometheus-client", "~> 4.2" +gem "meilisearch-rails", "~> 0.10.1" diff --git a/Gemfile.lock b/Gemfile.lock index c9dfd28f..ea379f90 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -249,6 +249,10 @@ GEM mail (2.7.1) mini_mime (>= 0.1.1) marcel (1.0.2) + meilisearch (0.25.1) + httparty (>= 0.17.1, < 0.22.0) + meilisearch-rails (0.10.1) + meilisearch (~> 0.25.0) method_source (1.0.0) mime-types (3.5.1) mime-types-data (~> 3.2015) @@ -535,6 +539,7 @@ DEPENDENCIES letter_opener lograge mail (~> 2.7.1) + meilisearch-rails (~> 0.10.1) mini_magick net-imap net-pop diff --git a/Procfile.dev b/Procfile.dev index bd494ae8..201c9d43 100644 --- a/Procfile.dev +++ b/Procfile.dev @@ -2,3 +2,4 @@ web: unset PORT && bin/rails server worker: bundle exec sidekiq css: yarn build:css --watch js: yarn build --watch +search: meilisearch --no-analytics --env development --db-path tmp/meilisearch/db --dump-dir tmp/meilisearch/dump --master-key justfordev42069e621 diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index 38cb84c6..a2cdf81c 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -1,9 +1,35 @@ class SearchController < ApplicationController def index @results = [] - query = params[:q] - return if query.blank? + @query = params[:q] + return if @query.blank? - @results = [] + @results = if params[:multi_search] == "1" + multi_search_experiment + else + [*Answer.search(@query), *Question.search(@query)] + end + end + + private + + def multi_search_experiment + MeiliSearch::Rails.client.multi_search( + [Answer, Question].map do |klass| + { + q: @query, + index_uid: klass.name.to_s, + show_ranking_score: true, + } + end + )["results"].flat_map do |h| + model = h["indexUid"].constantize # bad practice! + results = model.find(h["hits"].pluck("id")).map { |r| [r.id.to_s, r] }.to_h + h["hits"].map { |hit| [hit["_rankingScore"], results[hit["id"]]] } + end + .sort_by(&:first) + .reverse + .tap { |results| Rails.logger.debug(results) } + .map(&:last) end end diff --git a/app/models/answer.rb b/app/models/answer.rb index 71417252..e0be2475 100644 --- a/app/models/answer.rb +++ b/app/models/answer.rb @@ -1,6 +1,12 @@ class Answer < ApplicationRecord extend Answer::TimelineMethods + include MeiliSearch::Rails + + meilisearch do + attribute :content + end + belongs_to :user, counter_cache: :answered_count belongs_to :question, counter_cache: :answer_count has_many :comments, dependent: :destroy diff --git a/app/models/question.rb b/app/models/question.rb index 0a6adec0..505ec13e 100644 --- a/app/models/question.rb +++ b/app/models/question.rb @@ -1,6 +1,12 @@ class Question < ApplicationRecord include Question::AnswerMethods + include MeiliSearch::Rails + + meilisearch do + attribute :content + end + belongs_to :user, optional: true has_many :answers, dependent: :destroy has_many :inboxes, dependent: :destroy diff --git a/app/views/search/index.haml b/app/views/search/index.haml index 6f8280c9..a1e162f1 100644 --- a/app/views/search/index.haml +++ b/app/views/search/index.haml @@ -3,18 +3,19 @@ .col-sm-10.col-md-10.col-lg-9.mx-auto .card .card-body - = bootstrap_form_tag layout: :inline, method: :get do |f| - = f.text_field :q, skip_label: true, append: f.primary("Search") + = bootstrap_form_with url: search_path, layout: :inline, method: :get do |f| + = f.text_field :q, skip_label: true, append: f.primary("Search"), value: params[:q] + = f.check_box :multi_search, label: "Multisearch" - unless @results.blank? .container-lg.container--main .row .col-sm-10.col-md-10.col-lg-9.mx-auto - @results.each do |result| - - case result.searchable + - case result - when Answer - = render "answerbox", a: result.searchable, display_all: false, subscribed_answer_ids: [] + = render "answerbox", a: result, display_all: false, subscribed_answer_ids: [] - when Question - = render "shared/question", q: result.searchable, type: nil + = render "shared/question", q: result, type: nil = render 'shared/links' diff --git a/config/initializers/meilisearch.rb b/config/initializers/meilisearch.rb new file mode 100644 index 00000000..ac167bb1 --- /dev/null +++ b/config/initializers/meilisearch.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +return unless ENV["SEARCH_ENABLED"] == "true" + +MeiliSearch::Rails.configuration = { + meilisearch_url: ENV.fetch("MEILISEARCH_HOST", "http://localhost:7700"), + meilisearch_api_key: ENV.fetch("MEILISEARCH_API_KEY", "justfordev42069e621") +} diff --git a/db/schema.rb b/db/schema.rb index 26d80e0f..ea268655 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2023_10_22_155815) do +ActiveRecord::Schema.define(version: 2023_10_18_172518) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -127,15 +127,6 @@ ActiveRecord::Schema.define(version: 2023_10_22_155815) do t.index ["type"], name: "index_notifications_on_type" end - create_table "pg_search_documents", force: :cascade do |t| - t.text "content" - t.string "searchable_type" - t.bigint "searchable_id" - t.datetime "created_at", precision: 6, null: false - t.datetime "updated_at", precision: 6, null: false - t.index ["searchable_type", "searchable_id"], name: "index_pg_search_documents_on_searchable" - end - create_table "profiles", force: :cascade do |t| t.bigint "user_id" t.string "display_name" @@ -397,6 +388,5 @@ ActiveRecord::Schema.define(version: 2023_10_22_155815) do t.index ["user_id"], name: "index_web_push_subscriptions_on_user_id" end - add_foreign_key "anonymous_blocks", "users", column: "target_user_id" add_foreign_key "profiles", "users" end