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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
|
# frozen_string_literal: true
# == Subscribable concern
#
# Users can subscribe to these models.
#
# Used by Issue, MergeRequest, Label
#
module Subscribable
extend ActiveSupport::Concern
included do
has_many :subscriptions, dependent: :destroy, as: :subscribable # rubocop:disable Cop/ActiveRecordDependent
end
def subscribed?(user, project = nil)
return false unless user
if (subscription = lazy_subscription(user, project)&.itself)
subscription.subscribed
else
subscribed_without_subscriptions?(user, project)
end
end
def lazy_subscription(user, project = nil)
return unless user
BatchLoader.for(id: id, subscribable_type: subscribable_type, project_id: project&.id).batch do |items, loader|
values = items.each_with_object({ ids: Set.new, subscribable_types: Set.new, project_ids: Set.new }) do |item, result|
result[:ids] << item[:id]
result[:subscribable_types] << item[:subscribable_type]
result[:project_ids] << item[:project_id]
end
subscriptions = Subscription.where(subscribable_id: values[:ids], subscribable_type: values[:subscribable_types], project_id: values[:project_ids], user: user)
subscriptions.each do |subscription|
loader.call({
id: subscription.subscribable_id,
subscribable_type: subscription.subscribable_type,
project_id: subscription.project_id
}, subscription)
end
end
end
# Override this method to define custom logic to consider a subscribable as
# subscribed without an explicit subscription record.
def subscribed_without_subscriptions?(user, project)
false
end
def subscribers(project)
relation = subscriptions_available(project)
.where(subscribed: true)
.select(:user_id)
User.where(id: relation)
end
def toggle_subscription(user, project = nil)
unsubscribe_from_other_levels(user, project)
new_value = !subscribed?(user, project)
find_or_initialize_subscription(user, project)
.update(subscribed: new_value)
end
def subscribe(user, project = nil)
unsubscribe_from_other_levels(user, project)
find_or_initialize_subscription(user, project)
.update(subscribed: true)
end
def unsubscribe(user, project = nil)
unsubscribe_from_other_levels(user, project)
find_or_initialize_subscription(user, project)
.update(subscribed: false)
end
def set_subscription(user, desired_state, project = nil)
if desired_state
subscribe(user, project)
else
unsubscribe(user, project)
end
end
private
def unsubscribe_from_other_levels(user, project)
other_subscriptions = subscriptions.where(user: user)
other_subscriptions =
if project.blank?
other_subscriptions.where.not(project: nil)
else
other_subscriptions.where(project: nil)
end
other_subscriptions.update_all(subscribed: false)
end
def find_or_initialize_subscription(user, project)
BatchLoader::Executor.clear_current
subscriptions
.find_or_initialize_by(user_id: user.id, project_id: project.try(:id))
end
def subscriptions_available(project)
t = Subscription.arel_table
subscriptions
.where(t[:project_id].eq(nil).or(t[:project_id].eq(project.try(:id))))
end
def subscribable_type
# handle project and group labels as well as issuable subscriptions
if self.class.ancestors.include?(Label)
'Label'
elsif self.class.ancestors.include?(Issue)
'Issue'
else
self.class.name
end
end
end
|