discourse/lib/bookmark_query.rb
Martin Brennan 8e9164fb60
DEV: Minor bookmark tweaks for polymorphism (#16728)
* Make the modal for bookmarks display more consistently
* Make sure bookmark query can handle empty results for certain
  bookmarkable queries
2022-05-12 10:29:01 +10:00

136 lines
4.3 KiB
Ruby

# frozen_string_literal: true
##
# Allows us to query Bookmark records for lists. Used mainly
# in the user/activity/bookmarks page.
class BookmarkQuery
def self.on_preload(&blk)
(@preload ||= Set.new) << blk
end
def self.preload(bookmarks, object)
if SiteSetting.use_polymorphic_bookmarks
preload_polymorphic_associations(bookmarks)
end
if @preload
@preload.each { |preload| preload.call(bookmarks, object) }
end
end
# These polymorphic associations are loaded to make the UserBookmarkListSerializer's
# life easier, which conditionally chooses the bookmark serializer to use based
# on the type, and we want the associations all loaded ahead of time to make
# sure we are not doing N+1s.
def self.preload_polymorphic_associations(bookmarks)
Bookmark.registered_bookmarkables.each do |registered_bookmarkable|
registered_bookmarkable.perform_preload(bookmarks)
end
end
def initialize(user:, guardian: nil, params: {})
@user = user
@params = params
@guardian = guardian || Guardian.new(@user)
@page = @params[:page].to_i
@limit = @params[:limit].present? ? @params[:limit].to_i : @params[:per_page]
end
def list_all
return polymorphic_list_all if SiteSetting.use_polymorphic_bookmarks
topics = Topic.listable_topics.secured(@guardian)
pms = Topic.private_messages_for_user(@user)
results = list_all_results(topics, pms)
results = results.order(
"(CASE WHEN bookmarks.pinned THEN 0 ELSE 1 END),
bookmarks.reminder_at ASC,
bookmarks.updated_at DESC"
)
if @params[:q].present?
results = search(results, @params[:q])
end
if @page.positive?
results = results.offset(@page * @params[:per_page])
end
results = results.limit(@limit).to_a
BookmarkQuery.preload(results, self)
results
end
private
def polymorphic_list_all
search_term = @params[:q]
ts_query = search_term.present? ? Search.ts_query(term: search_term) : nil
search_term_wildcard = search_term.present? ? "%#{search_term}%" : nil
queries = Bookmark.registered_bookmarkables.map do |bookmarkable|
interim_results = bookmarkable.perform_list_query(@user, @guardian)
# this could occur if there is some security reason that the user cannot
# access the bookmarkables that they have bookmarked, e.g. if they had 1 bookmark
# on a topic and that topic was moved into a private category
next if interim_results.blank?
if search_term.present?
interim_results = bookmarkable.perform_search_query(
interim_results, search_term_wildcard, ts_query
)
end
# this is purely to make the query easy to read and debug, otherwise it's
# all mashed up into a massive ball in MiniProfiler :)
"---- #{bookmarkable.model.to_s} bookmarkable ---\n\n #{interim_results.to_sql}"
end.compact
# same for interim results being blank, the user might have been locked out
# from all their various bookmarks, in which case they will see nothing and
# no further pagination/ordering/etc is required
return [] if queries.empty?
union_sql = queries.join("\n\nUNION\n\n")
results = Bookmark.select("bookmarks.*").from("(\n\n#{union_sql}\n\n) as bookmarks")
results = results.order(
"(CASE WHEN bookmarks.pinned THEN 0 ELSE 1 END),
bookmarks.reminder_at ASC,
bookmarks.updated_at DESC"
)
if @page.positive?
results = results.offset(@page * @params[:per_page])
end
results = results.limit(@limit).to_a
BookmarkQuery.preload(results, self)
results
end
def list_all_results(topics, pms)
results = base_bookmarks.merge(topics.or(pms))
results = results.merge(Post.secured(@guardian))
results = @guardian.filter_allowed_categories(results)
results
end
def base_bookmarks
Bookmark.where(user: @user)
.includes(post: :user)
.includes(post: { topic: :tags })
.includes(topic: :topic_users)
.references(:post)
.where(topic_users: { user_id: @user.id })
end
def search(results, term)
bookmark_ts_query = Search.ts_query(term: term)
results
.joins("LEFT JOIN post_search_data ON post_search_data.post_id = bookmarks.post_id")
.where("bookmarks.name ILIKE :q OR #{bookmark_ts_query} @@ post_search_data.search_data", q: "%#{term}%")
end
end