From 6201f96b8a49d58b5b2d73fac9fa1fa93f5890ed Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Mon, 5 Jun 2017 10:07:44 -0400 Subject: Introduce StatusThreadingConcern (#3490) * Add a StatusFilter class to identify visibility of statuses by accounts * Extract StatusThreadingConcern from Status * Clarify purpose of checking for nil account --- app/models/concerns/status_threading_concern.rb | 89 +++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 app/models/concerns/status_threading_concern.rb (limited to 'app/models/concerns') diff --git a/app/models/concerns/status_threading_concern.rb b/app/models/concerns/status_threading_concern.rb new file mode 100644 index 000000000..65f8e112e --- /dev/null +++ b/app/models/concerns/status_threading_concern.rb @@ -0,0 +1,89 @@ +# frozen_string_literal: true + +module StatusThreadingConcern + extend ActiveSupport::Concern + + def ancestors(account = nil) + find_statuses_from_tree_path(ancestor_ids, account) + end + + def descendants(account = nil) + find_statuses_from_tree_path(descendant_ids, account) + end + + private + + def ancestor_ids + Rails.cache.fetch("ancestors:#{id}") do + ancestors_without_self.pluck(:id) + end + end + + def ancestors_without_self + ancestor_statuses - [self] + end + + def ancestor_statuses + Status.find_by_sql([<<-SQL.squish, id: id]) + WITH RECURSIVE search_tree(id, in_reply_to_id, path) + AS ( + SELECT id, in_reply_to_id, ARRAY[id] + FROM statuses + WHERE id = :id + UNION ALL + SELECT statuses.id, statuses.in_reply_to_id, path || statuses.id + FROM search_tree + JOIN statuses ON statuses.id = search_tree.in_reply_to_id + WHERE NOT statuses.id = ANY(path) + ) + SELECT id + FROM search_tree + ORDER BY path DESC + SQL + end + + def descendant_ids + descendants_without_self.pluck(:id) + end + + def descendants_without_self + descendant_statuses - [self] + end + + def descendant_statuses + Status.find_by_sql([<<-SQL.squish, id: id]) + WITH RECURSIVE search_tree(id, path) + AS ( + SELECT id, ARRAY[id] + FROM statuses + WHERE id = :id + UNION ALL + SELECT statuses.id, path || statuses.id + FROM search_tree + JOIN statuses ON statuses.in_reply_to_id = search_tree.id + WHERE NOT statuses.id = ANY(path) + ) + SELECT id + FROM search_tree + ORDER BY path + SQL + end + + def find_statuses_from_tree_path(ids, account) + statuses = statuses_with_accounts(ids).to_a + + # FIXME: n+1 bonanza + statuses.reject! { |status| filter_from_context?(status, account) } + + # Order ancestors/descendants by tree path + statuses.sort_by! { |status| ids.index(status.id) } + end + + def statuses_with_accounts(ids) + Status.where(id: ids).includes(:account) + end + + def filter_from_context?(status, account) + StatusFilter.new(status, account).filtered? + end +end -- cgit