* Working MVP * Add comment to COMMENTABLE_TYPES * Add specs to search_spec * Add service specs and fix ordering * Fixed queries and specs * Clarify COMMENTABLE_TYPES comment * Move FORCED_EAGER_LOAD_QUERY to Comment * Add highlighting to Comment search * Add explicit .to_s on class_name params * Remove Comment#forced_eager_load_serialized_data * Revert CommentSerializer changes * Hardcode class_name attribute * Add PR feedback * Use one liner syntax in serializer * Update user comment * Update path attribute logic * Fix user_ids * Remove PodcastEpisode skipped spec * Add sort order to Search::Postgres::Comment * Fix search_specs * Move pagination TODO comment
118 lines
4 KiB
Ruby
118 lines
4 KiB
Ruby
module Search
|
|
module Postgres
|
|
class Comment
|
|
ATTRIBUTES = [
|
|
"COALESCE(articles.published, false) AS commentable_published",
|
|
"COALESCE(articles.title, '') AS commentable_title",
|
|
"comments.body_markdown",
|
|
"comments.commentable_id",
|
|
"comments.commentable_type",
|
|
"comments.created_at",
|
|
"comments.id AS id",
|
|
"comments.public_reactions_count",
|
|
"comments.score",
|
|
"comments.user_id",
|
|
].freeze
|
|
private_constant :ATTRIBUTES
|
|
|
|
USER_ATTRIBUTES = %i[
|
|
id
|
|
name
|
|
profile_image
|
|
username
|
|
].freeze
|
|
private_constant :USER_ATTRIBUTES
|
|
|
|
ARTICLE_COMMENTABLE_QUERY = <<-SQL.freeze
|
|
LEFT JOIN articles
|
|
ON comments.commentable_id = articles.id
|
|
AND comments.commentable_type = 'Article'
|
|
SQL
|
|
private_constant :ARTICLE_COMMENTABLE_QUERY
|
|
|
|
DEFAULT_PER_PAGE = 60
|
|
private_constant :DEFAULT_PER_PAGE
|
|
|
|
DEFAULT_SORT_BY = "comments.score".freeze
|
|
private_constant :DEFAULT_SORT_BY
|
|
|
|
DEFAULT_SORT_DIRECTION = :desc
|
|
private_constant :DEFAULT_SORT_DIRECTION
|
|
|
|
MAX_PER_PAGE = 120 # to avoid querying too many items, we set a maximum amount for a page
|
|
private_constant :MAX_PER_PAGE
|
|
|
|
# We filter comments for those that are:
|
|
# 1. On Articles
|
|
# 2. Not deleted
|
|
# 3. Not hidden by commentable user (i.e. an Article author didn't hide the comment)
|
|
# 4. Are attached to published articles
|
|
QUERY_FILTER = <<-SQL.freeze
|
|
comments.commentable_type = 'Article' AND
|
|
comments.deleted = false AND
|
|
comments.hidden_by_commentable_user = false AND
|
|
articles.published = true
|
|
SQL
|
|
private_constant :QUERY_FILTER
|
|
|
|
def self.search_documents(
|
|
page: 0,
|
|
per_page: DEFAULT_PER_PAGE,
|
|
sort_by: DEFAULT_SORT_BY,
|
|
sort_direction: DEFAULT_SORT_DIRECTION,
|
|
term: nil
|
|
)
|
|
sort_by ||= DEFAULT_SORT_BY
|
|
# The UI and serializer rename created_at (the actual DB column name) to
|
|
# published_at
|
|
sort_by = "comments.created_at" if sort_by == "published_at"
|
|
|
|
sort_direction ||= DEFAULT_SORT_DIRECTION
|
|
|
|
# NOTE: [@rhymes/atsmith813] 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 = ::Comment.joins(ARTICLE_COMMENTABLE_QUERY).where(QUERY_FILTER)
|
|
|
|
relation = relation.search_comments(term).with_pg_search_highlight if term.present?
|
|
|
|
relation = relation.select(*ATTRIBUTES).reorder("#{sort_by}": sort_direction)
|
|
|
|
results = relation.page(page).per(per_page)
|
|
|
|
# NOTE: [@rhymes/atsmith813] an earlier version used `.includes(:user)`
|
|
# to preload users, unfortunately it's not possible in Rails to specify
|
|
# which fields of the included relation's table to select ahead of time.
|
|
# The `users` table is massive (115 columns on March 2021) and thus we
|
|
# shouldn't load it all in memory just to select a few fields.
|
|
# For these reasons I decided to avoid preloading altogether and issue
|
|
# an additional SQL query to load User objects
|
|
# (see https://github.com/forem/forem/pull/4744#discussion_r345698674
|
|
# and https://github.com/rails/rails/issues/15185#issuecomment-351868335
|
|
# for additional context)
|
|
user_ids = results.pluck(:user_id)
|
|
users = find_users(user_ids)
|
|
|
|
serialize(results, users)
|
|
end
|
|
|
|
def self.find_users(user_ids)
|
|
::User
|
|
.where(id: user_ids)
|
|
.select(*USER_ATTRIBUTES)
|
|
.index_by(&:id)
|
|
end
|
|
private_class_method :find_users
|
|
|
|
def self.serialize(results, users)
|
|
Search::PostgresCommentSerializer
|
|
.new(results, params: { users: users }, is_collection: true)
|
|
.serializable_hash[:data]
|
|
.pluck(:attributes)
|
|
end
|
|
private_class_method :serialize
|
|
end
|
|
end
|
|
end
|