module Search class User ATTRIBUTES = %i[ id name profile_image username ].freeze DEFAULT_PER_PAGE = 60 MAX_PER_PAGE = 100 HOTNESS_SCORE_ORDER = Arel.sql(%{ (((articles_count + comments_count + reactions_count + badge_achievements_count) * 10) * reputation_modifier) DESC }.squish).freeze def self.search_documents(term: nil, sort_by: :nil, sort_direction: :desc, page: 0, per_page: DEFAULT_PER_PAGE) # NOTE: we should eventually update the frontend # to start from page 1 page = page.to_i + 1 per_page = [(per_page || DEFAULT_PER_PAGE).to_i, MAX_PER_PAGE].min relation = ::User relation = filter_suspended_users(relation) relation = relation.search_by_name_and_username(term) if term.present? relation = relation.select(*ATTRIBUTES) relation = sort(relation, sort_by, sort_direction) relation = relation.page(page).per(per_page) serialize(relation) end # `User.without_role` generates a subquery + 2 inner joins. # Given that the number of suspended users will, hopefully, be a tiny percentage # of regular users, and the `rolify`'s gem approach is not particularly efficient, # we simplified the subquery and added a precondition to skip that query entirely, # when a community has no suspended users. # NOTE: An alternative approach that could be explored is to # preload the user ids of all suspended users and use those with `.where.not(id: ...)` def self.filter_suspended_users(relation) suspended = UserRole.joins(:role).where(roles: { name: :suspended }) return relation unless suspended.exists? relation.where.not(id: suspended.select(:user_id)) end private_class_method :filter_suspended_users def self.sort(relation, sort_by, sort_direction) return relation.reorder(sort_by => sort_direction) if sort_by&.to_sym == :created_at relation.reorder(HOTNESS_SCORE_ORDER) end private_class_method :sort def self.serialize(users) Search::SimpleUserSerializer .new(users, is_collection: true) .serializable_hash[:data] .pluck(:attributes) end private_class_method :serialize end end