Fix n+1 queries in StatusThreadingConcern (#7321)
This commit is contained in:
		
							parent
							
								
									a3d84e705a
								
							
						
					
					
						commit
						a5293fdf61
					
				
					 4 changed files with 74 additions and 20 deletions
				
			
		| 
						 | 
					@ -3,9 +3,10 @@
 | 
				
			||||||
class StatusFilter
 | 
					class StatusFilter
 | 
				
			||||||
  attr_reader :status, :account
 | 
					  attr_reader :status, :account
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def initialize(status, account)
 | 
					  def initialize(status, account, preloaded_relations = {})
 | 
				
			||||||
    @status = status
 | 
					    @status              = status
 | 
				
			||||||
    @account = account
 | 
					    @account             = account
 | 
				
			||||||
 | 
					    @preloaded_relations = preloaded_relations
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def filtered?
 | 
					  def filtered?
 | 
				
			||||||
| 
						 | 
					@ -24,15 +25,15 @@ class StatusFilter
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def blocking_account?
 | 
					  def blocking_account?
 | 
				
			||||||
    account.blocking? status.account_id
 | 
					    @preloaded_relations[:blocking] ? @preloaded_relations[:blocking][status.account_id] : account.blocking?(status.account_id)
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def blocking_domain?
 | 
					  def blocking_domain?
 | 
				
			||||||
    account.domain_blocking? status.account_domain
 | 
					    @preloaded_relations[:domain_blocking_by_domain] ? @preloaded_relations[:domain_blocking_by_domain][status.account_domain] : account.domain_blocking?(status.account_domain)
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def muting_account?
 | 
					  def muting_account?
 | 
				
			||||||
    account.muting? status.account_id
 | 
					    @preloaded_relations[:muting] ? @preloaded_relations[:muting][status.account_id] : account.muting?(status.account_id)
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def silenced_account?
 | 
					  def silenced_account?
 | 
				
			||||||
| 
						 | 
					@ -44,7 +45,7 @@ class StatusFilter
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def account_following_status_account?
 | 
					  def account_following_status_account?
 | 
				
			||||||
    account&.following? status.account_id
 | 
					    @preloaded_relations[:following] ? @preloaded_relations[:following][status.account_id] : account&.following?(status.account_id)
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def blocked_by_policy?
 | 
					  def blocked_by_policy?
 | 
				
			||||||
| 
						 | 
					@ -52,6 +53,6 @@ class StatusFilter
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def policy_allows_show?
 | 
					  def policy_allows_show?
 | 
				
			||||||
    StatusPolicy.new(account, status).show?
 | 
					    StatusPolicy.new(account, status, @preloaded_relations).show?
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -20,6 +20,10 @@ module AccountInteractions
 | 
				
			||||||
      follow_mapping(Block.where(target_account_id: target_account_ids, account_id: account_id), :target_account_id)
 | 
					      follow_mapping(Block.where(target_account_id: target_account_ids, account_id: account_id), :target_account_id)
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def blocked_by_map(target_account_ids, account_id)
 | 
				
			||||||
 | 
					      follow_mapping(Block.where(account_id: target_account_ids, target_account_id: account_id), :account_id)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def muting_map(target_account_ids, account_id)
 | 
					    def muting_map(target_account_ids, account_id)
 | 
				
			||||||
      Mute.where(target_account_id: target_account_ids, account_id: account_id).each_with_object({}) do |mute, mapping|
 | 
					      Mute.where(target_account_id: target_account_ids, account_id: account_id).each_with_object({}) do |mute, mapping|
 | 
				
			||||||
        mapping[mute.target_account_id] = {
 | 
					        mapping[mute.target_account_id] = {
 | 
				
			||||||
| 
						 | 
					@ -38,8 +42,12 @@ module AccountInteractions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def domain_blocking_map(target_account_ids, account_id)
 | 
					    def domain_blocking_map(target_account_ids, account_id)
 | 
				
			||||||
      accounts_map    = Account.where(id: target_account_ids).select('id, domain').map { |a| [a.id, a.domain] }.to_h
 | 
					      accounts_map    = Account.where(id: target_account_ids).select('id, domain').map { |a| [a.id, a.domain] }.to_h
 | 
				
			||||||
      blocked_domains = AccountDomainBlock.where(account_id: account_id, domain: accounts_map.values).pluck(:domain)
 | 
					      blocked_domains = domain_blocking_map_by_domain(accounts_map.values.compact, account_id)
 | 
				
			||||||
      accounts_map.map { |id, domain| [id, blocked_domains.include?(domain)] }.to_h
 | 
					      accounts_map.map { |id, domain| [id, blocked_domains[domain]] }.to_h
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def domain_blocking_map_by_domain(target_domains, account_id)
 | 
				
			||||||
 | 
					      follow_mapping(AccountDomainBlock.where(account_id: account_id, domain: target_domains), :domain)
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private
 | 
					    private
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -71,10 +71,21 @@ module StatusThreadingConcern
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def find_statuses_from_tree_path(ids, account)
 | 
					  def find_statuses_from_tree_path(ids, account)
 | 
				
			||||||
    statuses = statuses_with_accounts(ids).to_a
 | 
					    statuses    = statuses_with_accounts(ids).to_a
 | 
				
			||||||
 | 
					    account_ids = statuses.map(&:account_id).uniq
 | 
				
			||||||
 | 
					    domains     = statuses.map(&:account_domain).compact.uniq
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # FIXME: n+1 bonanza
 | 
					    relations = if account.present?
 | 
				
			||||||
    statuses.reject! { |status| filter_from_context?(status, account) }
 | 
					                  {
 | 
				
			||||||
 | 
					                    blocking: Account.blocking_map(account_ids, account.id),
 | 
				
			||||||
 | 
					                    blocked_by: Account.blocked_by_map(account_ids, account.id),
 | 
				
			||||||
 | 
					                    muting: Account.muting_map(account_ids, account.id),
 | 
				
			||||||
 | 
					                    following: Account.following_map(account_ids, account.id),
 | 
				
			||||||
 | 
					                    domain_blocking_by_domain: Account.domain_blocking_map_by_domain(domains, account.id),
 | 
				
			||||||
 | 
					                  }
 | 
				
			||||||
 | 
					                end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    statuses.reject! { |status| filter_from_context?(status, account, relations) }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Order ancestors/descendants by tree path
 | 
					    # Order ancestors/descendants by tree path
 | 
				
			||||||
    statuses.sort_by! { |status| ids.index(status.id) }
 | 
					    statuses.sort_by! { |status| ids.index(status.id) }
 | 
				
			||||||
| 
						 | 
					@ -84,7 +95,7 @@ module StatusThreadingConcern
 | 
				
			||||||
    Status.where(id: ids).includes(:account)
 | 
					    Status.where(id: ids).includes(:account)
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def filter_from_context?(status, account)
 | 
					  def filter_from_context?(status, account, relations)
 | 
				
			||||||
    StatusFilter.new(status, account).filtered?
 | 
					    StatusFilter.new(status, account, relations).filtered?
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,26 +1,32 @@
 | 
				
			||||||
# frozen_string_literal: true
 | 
					# frozen_string_literal: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class StatusPolicy < ApplicationPolicy
 | 
					class StatusPolicy < ApplicationPolicy
 | 
				
			||||||
 | 
					  def initialize(current_account, record, preloaded_relations = {})
 | 
				
			||||||
 | 
					    super(current_account, record)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @preloaded_relations = preloaded_relations
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def index?
 | 
					  def index?
 | 
				
			||||||
    staff?
 | 
					    staff?
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def show?
 | 
					  def show?
 | 
				
			||||||
    if direct?
 | 
					    if direct?
 | 
				
			||||||
      owned? || record.mentions.where(account: current_account).exists?
 | 
					      owned? || mention_exists?
 | 
				
			||||||
    elsif private?
 | 
					    elsif private?
 | 
				
			||||||
      owned? || current_account&.following?(author) || record.mentions.where(account: current_account).exists?
 | 
					      owned? || following_author? || mention_exists?
 | 
				
			||||||
    else
 | 
					    else
 | 
				
			||||||
      current_account.nil? || !author.blocking?(current_account)
 | 
					      current_account.nil? || !author_blocking?
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def reblog?
 | 
					  def reblog?
 | 
				
			||||||
    !direct? && (!private? || owned?) && show? && !current_account&.blocking?(author)
 | 
					    !direct? && (!private? || owned?) && show? && !blocking_author?
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def favourite?
 | 
					  def favourite?
 | 
				
			||||||
    show? && !current_account&.blocking?(author)
 | 
					    show? && !blocking_author?
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def destroy?
 | 
					  def destroy?
 | 
				
			||||||
| 
						 | 
					@ -47,6 +53,34 @@ class StatusPolicy < ApplicationPolicy
 | 
				
			||||||
    record.private_visibility?
 | 
					    record.private_visibility?
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def mention_exists?
 | 
				
			||||||
 | 
					    return false if current_account.nil?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if record.mentions.loaded?
 | 
				
			||||||
 | 
					      record.mentions.any? { |mention| mention.account_id == current_account.id }
 | 
				
			||||||
 | 
					    else
 | 
				
			||||||
 | 
					      record.mentions.where(account: current_account).exists?
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def blocking_author?
 | 
				
			||||||
 | 
					    return false if current_account.nil?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @preloaded_relations[:blocking] ? @preloaded_relations[:blocking][author.id] : current_account.blocking?(author)
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def author_blocking?
 | 
				
			||||||
 | 
					    return false if current_account.nil?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @preloaded_relations[:blocked_by] ? @preloaded_relations[:blocked_by][author.id] : author.blocking?(current_account)
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def following_author?
 | 
				
			||||||
 | 
					    return false if current_account.nil?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @preloaded_relations[:following] ? @preloaded_relations[:following][author.id] : current_account.following?(author)
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def author
 | 
					  def author
 | 
				
			||||||
    record.account
 | 
					    record.account
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in a new issue