Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add AlgoliaSearchable for User #20869

Merged
merged 13 commits into from Apr 19, 2024
14 changes: 14 additions & 0 deletions app/models/concerns/algolia_searchable.rb
@@ -0,0 +1,14 @@
module AlgoliaSearchable
extend ActiveSupport::Concern

# TODO: Make sure trigger_sidekiq_worker is called in ALL important places, ie all update_column area
DEFAULT_ALGOLIA_SETTINGS = {
per_environment: true,
disable_indexing: -> { Settings::General.algolia_search_enabled? == false },
enqueue: :trigger_sidekiq_worker
}.freeze

included do
public_send :include, "AlgoliaSearchable::Searchable#{name}".constantize
end
end
30 changes: 30 additions & 0 deletions app/models/concerns/algolia_searchable/searchable_user.rb
@@ -0,0 +1,30 @@
module AlgoliaSearchable
module SearchableUser
extend ActiveSupport::Concern

included do
include AlgoliaSearch

algoliasearch(**DEFAULT_ALGOLIA_SETTINGS, unless: :bad_actor) do
attribute :name, :username
attribute :profile_image do
profile_image_90
end
end
end

class_methods do
def trigger_sidekiq_worker(record, delete)
AlgoliaSearch::SearchIndexWorker.perform_async(record.class.name, record.id, delete)
end
end

def bad_actor
score.negative?
end

def bad_actor_changed?
score_changed? && score_was.negative? != score.negative?
end
end
end
1 change: 1 addition & 0 deletions app/models/user.rb
Expand Up @@ -5,6 +5,7 @@ class User < ApplicationRecord
include CloudinaryHelper

include Images::Profile.for(:profile_image_url)
include AlgoliaSearchable

# NOTE: we are using an inline module to keep profile related things together.
concerning :Profiles do
Expand Down
17 changes: 17 additions & 0 deletions app/workers/algolia_search/search_index_worker.rb
@@ -0,0 +1,17 @@
module AlgoliaSearch
class SearchIndexWorker
include Sidekiq::Worker
sidekiq_options queue: :algolia_indexing, retry: 5

def perform(klass, id, remove)
record = klass.constantize

if remove
index = AlgoliaSearch.client.init_index(record.index_name)
index.delete_object(id)
else
record.find(id).index!
end
end
end
end
1 change: 1 addition & 0 deletions config/sidekiq.yml
Expand Up @@ -2,6 +2,7 @@
- ["default", 1]
- ["low_priority", 10]
- ["medium_priority", 100]
- ["algolia_indexing", 500]
maestromac marked this conversation as resolved.
Show resolved Hide resolved
- ["high_priority", 1000]
- ["scheduler", 1000]
- ["mailers", 1000]
22 changes: 22 additions & 0 deletions spec/models/user_spec.rb
Expand Up @@ -1022,4 +1022,26 @@ def provider_username(service_name)
end
end
end

describe "Algolia indexing", :algolia do
it "indexes the user" do
allow(AlgoliaSearch::SearchIndexWorker).to receive(:perform_async)
create(:user)
expect(AlgoliaSearch::SearchIndexWorker).to have_received(:perform_async).with("User", kind_of(Integer), false)
end

it "does not index the user on update if nothing changed" do
user = create(:user)
allow(AlgoliaSearch::SearchIndexWorker).to receive(:perform_async)
user.save
expect(AlgoliaSearch::SearchIndexWorker).not_to have_received(:perform_async)
end

it "updates user index if user has changed" do
user = create(:user)
allow(AlgoliaSearch::SearchIndexWorker).to receive(:perform_async)
user.update(name: "New Name")
expect(AlgoliaSearch::SearchIndexWorker).to have_received(:perform_async).with("User", user.id, false)
end
end
end
6 changes: 6 additions & 0 deletions spec/rails_helper.rb
Expand Up @@ -128,6 +128,12 @@
end
end

config.before(:each, :algolia) do
allow(Settings::General).to receive_messages(
algolia_application_id: "on", algolia_search_only_api_key: "on", algolia_api_key: "on",
)
end

config.before(:suite) do
# Set the TZ ENV variable with the current random timezone from zonebie
# which we can then use to properly set the browser time for Capybara specs
Expand Down
65 changes: 65 additions & 0 deletions spec/workers/algolia_search/search_index_worker_spec.rb
@@ -0,0 +1,65 @@
require "rails_helper"

# from https://github.com/algolia/algoliasearch-client-ruby/blob/master/test/algolia/integration/mocks/mock_requester.rb
class MockRequester
attr_accessor :requests

def initialize
@connection = nil
@requests = []
end

def send_request(host, method, path, body, headers, timeout, connect_timeout)
request = {
host: host,
method: method,
path: path,
body: body,
headers: headers,
timeout: timeout,
connect_timeout: connect_timeout
}

@requests.push(request)

Algolia::Http::Response.new(
status: 200,
body: '{"hits": [], "status": "published"}',
headers: {},
)
end

def get_connection(host)
@connection = host
end

def build_url(host)
host.protocol + host.url
end
end
# rubocop:disable RSpec/AnyInstance
RSpec.describe AlgoliaSearch::SearchIndexWorker, type: :worker do
let(:user) { create(:user) }

before do
mock_requester = MockRequester.new
algolia_config = Algolia::Search::Config.new(AlgoliaSearch.configuration)
mock_client = Algolia::Search::Client.new(algolia_config, http_requester: mock_requester)

AlgoliaSearch.instance_variable_set(:@client, mock_client)
end

it "remove the record from Algolia if record is deleted" do
expect_any_instance_of(Algolia::Search::Index).to receive(:delete_object).with(user.id)

described_class.new.perform(user.class.name, user.id, true)
end

it "index the record in Algolia if record is created" do
allow(User).to receive(:find).with(user.id).and_return(user)
allow(user).to receive(:index!)
described_class.new.perform(user.class.name, user.id, false)
expect(user).to have_received(:index!)
end
end
# rubocop:enable RSpec/AnyInstance