From d6fb96b9276d1a9edfae261d2eba2f79f8a9f340 Mon Sep 17 00:00:00 2001
From: Douwe Maan <douwe@gitlab.com>
Date: Wed, 14 Oct 2015 09:17:05 +0200
Subject: [PATCH] Have Issue#participants load all users mentioned in notes
 using a single query

---
 app/models/concerns/mentionable.rb  |  8 ++++----
 app/models/concerns/participable.rb | 23 +++++++++++++----------
 lib/gitlab/reference_extractor.rb   | 21 +++++++++++----------
 3 files changed, 28 insertions(+), 24 deletions(-)

diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb
index 7ad8d5b7da7..9339ecc4bce 100644
--- a/app/models/concerns/mentionable.rb
+++ b/app/models/concerns/mentionable.rb
@@ -47,19 +47,19 @@ module Mentionable
     SystemNoteService.cross_reference_exists?(target, local_reference)
   end
 
-  def mentioned_users(current_user = nil)
+  def mentioned_users(current_user = nil, load_lazy_references: true)
     return [] if mentionable_text.blank?
 
-    ext = Gitlab::ReferenceExtractor.new(self.project, current_user)
+    ext = Gitlab::ReferenceExtractor.new(self.project, current_user, load_lazy_references: load_lazy_references)
     ext.analyze(mentionable_text)
     ext.users.uniq
   end
 
   # Extract GFM references to other Mentionables from this Mentionable. Always excludes its #local_reference.
-  def references(p = project, current_user = self.author, text = mentionable_text)
+  def references(p = project, current_user = self.author, text = mentionable_text, load_lazy_references: true)
     return [] if text.blank?
 
-    ext = Gitlab::ReferenceExtractor.new(p, current_user)
+    ext = Gitlab::ReferenceExtractor.new(p, current_user, load_lazy_references: load_lazy_references)
     ext.analyze(text)
     (ext.issues + ext.merge_requests + ext.commits).uniq - [local_reference]
   end
diff --git a/app/models/concerns/participable.rb b/app/models/concerns/participable.rb
index 7c9597333dd..7a2bea567df 100644
--- a/app/models/concerns/participable.rb
+++ b/app/models/concerns/participable.rb
@@ -27,7 +27,7 @@ module Participable
 
   module ClassMethods
     def participant(*attrs)
-      participant_attrs.concat(attrs.map(&:to_s))
+      participant_attrs.concat(attrs)
     end
 
     def participant_attrs
@@ -37,13 +37,12 @@ module Participable
 
   # Be aware that this method makes a lot of sql queries.
   # Save result into variable if you are going to reuse it inside same request
-  def participants(current_user = self.author, project = self.project)
+  def participants(current_user = self.author, project = self.project, load_lazy_references: true)
     participants = self.class.participant_attrs.flat_map do |attr|
       meth = method(attr)
-
       value =
-        if meth.arity == 1 || meth.arity == -1
-          meth.call(current_user)
+        if attr == :mentioned_users
+          meth.call(current_user, load_lazy_references: false)
         else
           meth.call
         end
@@ -51,9 +50,13 @@ module Participable
       participants_for(value, current_user, project)
     end.compact.uniq
 
-    if project
-      participants.select! do |user|
-        user.can?(:read_project, project)
+    if load_lazy_references
+      participants = Gitlab::Markdown::ReferenceFilter::LazyReference.load(participants).uniq
+
+      if project
+        participants.select! do |user|
+          user.can?(:read_project, project)
+        end
       end
     end
 
@@ -64,12 +67,12 @@ module Participable
 
   def participants_for(value, current_user = nil, project = nil)
     case value
-    when User
+    when User, Gitlab::Markdown::ReferenceFilter::LazyReference
       [value]
     when Enumerable, ActiveRecord::Relation
       value.flat_map { |v| participants_for(v, current_user, project) }
     when Participable
-      value.participants(current_user, project)
+      value.participants(current_user, project, load_lazy_references: false)
     end
   end
 end
diff --git a/lib/gitlab/reference_extractor.rb b/lib/gitlab/reference_extractor.rb
index d6b739d7b9a..895a23ddcc8 100644
--- a/lib/gitlab/reference_extractor.rb
+++ b/lib/gitlab/reference_extractor.rb
@@ -3,17 +3,17 @@ require 'gitlab/markdown'
 module Gitlab
   # Extract possible GFM references from an arbitrary String for further processing.
   class ReferenceExtractor
-    attr_accessor :project, :current_user
+    attr_accessor :project, :current_user, :load_lazy_references
 
-    def initialize(project, current_user = nil)
+    def initialize(project, current_user = nil, load_lazy_references: true)
       @project = project
       @current_user = current_user
+      @load_lazy_references = load_lazy_references
     end
 
-    def analyze(texts)
+    def analyze(text)
       references.clear
-      texts = Array(texts)
-      @texts = texts.map { |text| Gitlab::Markdown.render_without_gfm(text) }
+      @text = Gitlab::Markdown.render_without_gfm(text)
     end
 
     %i(user label issue merge_request snippet commit commit_range).each do |type|
@@ -29,7 +29,7 @@ module Gitlab
         type = type.to_sym
         return references[type] if references.has_key?(type)
 
-        references[type] = pipeline_result(type).uniq
+        references[type] = pipeline_result(type)
       end
     end
 
@@ -53,14 +53,15 @@ module Gitlab
       }
 
       pipeline = HTML::Pipeline.new([filter, Gitlab::Markdown::ReferenceGathererFilter], context)
+      result = pipeline.call(@text)
 
-      values = @texts.flat_map do |text|
-        result = pipeline.call(text)
+      values = result[:references][filter_type].uniq
 
-        result[:references][filter_type]
+      if @load_lazy_references
+        values = Gitlab::Markdown::ReferenceFilter::LazyReference.load(values).uniq
       end
 
-      Gitlab::Markdown::ReferenceFilter::LazyReference.load(values)
+      values
     end
   end
 end
-- 
GitLab