1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# == Participable concern
#
# Contains functionality related to objects that can have participants, such as
# an author, an assignee and people mentioned in its description or comments.
#
# Usage:
#
# class Issue < ActiveRecord::Base
# include Participable
#
# # ...
#
# participant :author
# participant :assignee
# participant :notes
#
# participant -> (current_user, ext) do
# ext.analyze('...')
# end
# end
#
# issue = Issue.last
# users = issue.participants
module Participable
extend ActiveSupport::Concern
module ClassMethods
# Adds a list of participant attributes. Attributes can either be symbols or
# Procs.
#
# When using a Proc instead of a Symbol the Proc will be given two
# arguments:
#
# 1. The current user (as an instance of User)
# 2. An instance of `Gitlab::ReferenceExtractor`
#
# It is expected that a Proc populates the given reference extractor
# instance with data. The return value of the Proc is ignored.
#
# attr - The name of the attribute or a Proc
def participant(attr)
participant_attrs << attr
end
end
included do
# Accessor for participant attributes.
cattr_accessor :participant_attrs, instance_accessor: false do
[]
end
end
# Returns the users participating in a discussion.
#
# This method processes attributes of objects in breadth-first order.
#
# Returns an Array of User instances.
def participants(current_user = nil)
@participants ||= Hash.new do |hash, user|
hash[user] = raw_participants(user)
end
@participants[current_user]
end
private
def raw_participants(current_user = nil)
current_user ||= author
ext = Gitlab::ReferenceExtractor.new(project, current_user)
participants = Set.new
process = [self]
until process.empty?
source = process.pop
case source
when User
participants << source
when Participable
source.class.participant_attrs.each do |attr|
if attr.respond_to?(:call)
source.instance_exec(current_user, ext, &attr)
else
process << source.__send__(attr)
end
end
when Enumerable, ActiveRecord::Relation
# This uses reverse_each so we can use "pop" to get the next value to
# process (in order). Using unshift instead of pop would require
# moving all Array values one index to the left (which can be
# expensive).
source.reverse_each { |obj| process << obj }
end
end
participants.merge(ext.users)
Ability.users_that_can_read_project(participants.to_a, project)
end
end