account_search_query_builder.rb 4.91 KiB
# frozen_string_literal: true
class AccountSearchQueryBuilder
  DISALLOWED_TSQUERY_CHARACTERS = /['?\\:‘’]/.freeze
  LANGUAGE     = Arel::Nodes.build_quoted('simple').freeze
  EMPTY_STRING = Arel::Nodes.build_quoted('').freeze
  WEIGHT_A     = Arel::Nodes.build_quoted('A').freeze
  WEIGHT_B     = Arel::Nodes.build_quoted('B').freeze
  WEIGHT_C     = Arel::Nodes.build_quoted('C').freeze
  FIELDS = {
    display_name: { weight: WEIGHT_A }.freeze,
    username:     { weight: WEIGHT_B }.freeze,
    domain:       { weight: WEIGHT_C, nullable: true }.freeze,
  }.freeze
  RANK_NORMALIZATION = 32
  DEFAULT_OPTIONS = {
    limit: 10,
    only_following: false,
  }.freeze
  # @param [String] terms
  # @param [Hash] options
  # @option [Account] :account
  # @option [Boolean] :only_following
  # @option [Integer] :limit
  # @option [Integer] :offset
  def initialize(terms, options = {})
    @terms   = terms
    @options = DEFAULT_OPTIONS.merge(options)
  end
  # @return [ActiveRecord::Relation]
  def build
    search_scope.tap do |scope|
      scope.merge!(personalization_scope) if with_account?
      if with_account? && only_following?
        scope.merge!(only_following_scope)
        scope.with!(first_degree_definition) # `merge!` does not handle `with`
      end
    end
  end
  # @return [Array<Account>]
  def results
    build.to_a
  end
  private
  def search_scope
    Account.select(projections)
           .where(match_condition)
           .searchable
           .includes(:account_stat)
           .order(rank: :desc)
           .limit(limit)
           .offset(offset)
  end
  def personalization_scope
    join_condition = accounts_table.join(follows_table, Arel::Nodes::OuterJoin)
                                   .on(accounts_table.grouping(accounts_table[:id].eq(follows_table[:account_id]).and(follows_table[:target_account_id].eq(account.id))).or(accounts_table.grouping(accounts_table[:id].eq(follows_table[:target_account_id]).and(follows_table[:account_id].eq(account.id)))))
                                   .join_sources
    Account.joins(join_condition)