Fix n+1 queries in StatusThreadingConcern (#7321)
This commit is contained in:
		| @ -3,9 +3,10 @@ | ||||
| class StatusFilter | ||||
|   attr_reader :status, :account | ||||
|  | ||||
|   def initialize(status, account) | ||||
|     @status = status | ||||
|     @account = account | ||||
|   def initialize(status, account, preloaded_relations = {}) | ||||
|     @status              = status | ||||
|     @account             = account | ||||
|     @preloaded_relations = preloaded_relations | ||||
|   end | ||||
|  | ||||
|   def filtered? | ||||
| @ -24,15 +25,15 @@ class StatusFilter | ||||
|   end | ||||
|  | ||||
|   def blocking_account? | ||||
|     account.blocking? status.account_id | ||||
|     @preloaded_relations[:blocking] ? @preloaded_relations[:blocking][status.account_id] : account.blocking?(status.account_id) | ||||
|   end | ||||
|  | ||||
|   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 | ||||
|  | ||||
|   def muting_account? | ||||
|     account.muting? status.account_id | ||||
|     @preloaded_relations[:muting] ? @preloaded_relations[:muting][status.account_id] : account.muting?(status.account_id) | ||||
|   end | ||||
|  | ||||
|   def silenced_account? | ||||
| @ -44,7 +45,7 @@ class StatusFilter | ||||
|   end | ||||
|  | ||||
|   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 | ||||
|  | ||||
|   def blocked_by_policy? | ||||
| @ -52,6 +53,6 @@ class StatusFilter | ||||
|   end | ||||
|  | ||||
|   def policy_allows_show? | ||||
|     StatusPolicy.new(account, status).show? | ||||
|     StatusPolicy.new(account, status, @preloaded_relations).show? | ||||
|   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) | ||||
|     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) | ||||
|       Mute.where(target_account_id: target_account_ids, account_id: account_id).each_with_object({}) do |mute, mapping| | ||||
|         mapping[mute.target_account_id] = { | ||||
| @ -38,8 +42,12 @@ module AccountInteractions | ||||
|  | ||||
|     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 | ||||
|       blocked_domains = AccountDomainBlock.where(account_id: account_id, domain: accounts_map.values).pluck(:domain) | ||||
|       accounts_map.map { |id, domain| [id, blocked_domains.include?(domain)] }.to_h | ||||
|       blocked_domains = domain_blocking_map_by_domain(accounts_map.values.compact, account_id) | ||||
|       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 | ||||
|  | ||||
|     private | ||||
|  | ||||
| @ -71,10 +71,21 @@ module StatusThreadingConcern | ||||
|   end | ||||
|  | ||||
|   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 | ||||
|     statuses.reject! { |status| filter_from_context?(status, account) } | ||||
|     relations = if account.present? | ||||
|                   { | ||||
|                     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 | ||||
|     statuses.sort_by! { |status| ids.index(status.id) } | ||||
| @ -84,7 +95,7 @@ module StatusThreadingConcern | ||||
|     Status.where(id: ids).includes(:account) | ||||
|   end | ||||
|  | ||||
|   def filter_from_context?(status, account) | ||||
|     StatusFilter.new(status, account).filtered? | ||||
|   def filter_from_context?(status, account, relations) | ||||
|     StatusFilter.new(status, account, relations).filtered? | ||||
|   end | ||||
| end | ||||
|  | ||||
| @ -1,26 +1,32 @@ | ||||
| # frozen_string_literal: true | ||||
|  | ||||
| class StatusPolicy < ApplicationPolicy | ||||
|   def initialize(current_account, record, preloaded_relations = {}) | ||||
|     super(current_account, record) | ||||
|  | ||||
|     @preloaded_relations = preloaded_relations | ||||
|   end | ||||
|  | ||||
|   def index? | ||||
|     staff? | ||||
|   end | ||||
|  | ||||
|   def show? | ||||
|     if direct? | ||||
|       owned? || record.mentions.where(account: current_account).exists? | ||||
|       owned? || mention_exists? | ||||
|     elsif private? | ||||
|       owned? || current_account&.following?(author) || record.mentions.where(account: current_account).exists? | ||||
|       owned? || following_author? || mention_exists? | ||||
|     else | ||||
|       current_account.nil? || !author.blocking?(current_account) | ||||
|       current_account.nil? || !author_blocking? | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   def reblog? | ||||
|     !direct? && (!private? || owned?) && show? && !current_account&.blocking?(author) | ||||
|     !direct? && (!private? || owned?) && show? && !blocking_author? | ||||
|   end | ||||
|  | ||||
|   def favourite? | ||||
|     show? && !current_account&.blocking?(author) | ||||
|     show? && !blocking_author? | ||||
|   end | ||||
|  | ||||
|   def destroy? | ||||
| @ -47,6 +53,34 @@ class StatusPolicy < ApplicationPolicy | ||||
|     record.private_visibility? | ||||
|   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 | ||||
|     record.account | ||||
|   end | ||||
|  | ||||
		Reference in New Issue
	
	Block a user