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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
|
# frozen_string_literal: true
# == Schema Information
#
# Table name: notifications
#
# id :bigint(8) not null, primary key
# activity_id :bigint(8) not null
# activity_type :string not null
# created_at :datetime not null
# updated_at :datetime not null
# account_id :bigint(8) not null
# from_account_id :bigint(8) not null
# type :string
#
class Notification < ApplicationRecord
self.inheritance_column = nil
include Paginable
LEGACY_TYPE_CLASS_MAP = {
'Mention' => :mention,
'Status' => :reblog,
'Follow' => :follow,
'FollowRequest' => :follow_request,
'Favourite' => :favourite,
'Poll' => :poll,
}.freeze
TYPES = %i(
mention
status
reblog
follow
follow_request
favourite
poll
update
admin.sign_up
admin.report
).freeze
TARGET_STATUS_INCLUDES_BY_TYPE = {
status: :status,
reblog: [status: :reblog],
mention: [mention: :status],
favourite: [favourite: :status],
poll: [poll: :status],
update: :status,
'admin.report': [report: :target_account],
}.freeze
belongs_to :account, optional: true
belongs_to :from_account, class_name: 'Account', optional: true
belongs_to :activity, polymorphic: true, optional: true
belongs_to :mention, foreign_key: 'activity_id', optional: true
belongs_to :status, foreign_key: 'activity_id', optional: true
belongs_to :follow, foreign_key: 'activity_id', optional: true
belongs_to :follow_request, foreign_key: 'activity_id', optional: true
belongs_to :favourite, foreign_key: 'activity_id', optional: true
belongs_to :poll, foreign_key: 'activity_id', optional: true
belongs_to :report, foreign_key: 'activity_id', optional: true
validates :type, inclusion: { in: TYPES }
scope :without_suspended, -> { joins(:from_account).merge(Account.without_suspended) }
def type
@type ||= (super || LEGACY_TYPE_CLASS_MAP[activity_type]).to_sym
end
def target_status
case type
when :status, :update
status
when :reblog
status&.reblog
when :favourite
favourite&.status
when :mention
mention&.status
when :poll
poll&.status
end
end
class << self
def browserable(types: [], exclude_types: [], from_account_id: nil)
requested_types = begin
if types.empty?
TYPES
else
types.map(&:to_sym) & TYPES
end
end
requested_types -= exclude_types.map(&:to_sym)
all.tap do |scope|
scope.merge!(where(from_account_id: from_account_id)) if from_account_id.present?
scope.merge!(where(type: requested_types)) unless requested_types.size == TYPES.size
end
end
def preload_cache_collection_target_statuses(notifications, &_block)
notifications.group_by(&:type).each do |type, grouped_notifications|
associations = TARGET_STATUS_INCLUDES_BY_TYPE[type]
next unless associations
# Instead of using the usual `includes`, manually preload each type.
# If polymorphic associations are loaded with the usual `includes`, other types of associations will be loaded more.
ActiveRecord::Associations::Preloader.new.preload(grouped_notifications, associations)
end
unique_target_statuses = notifications.map(&:target_status).compact.uniq
# Call cache_collection in block
cached_statuses_by_id = yield(unique_target_statuses).index_by(&:id)
notifications.each do |notification|
next if notification.target_status.nil?
cached_status = cached_statuses_by_id[notification.target_status.id]
case notification.type
when :status, :update
notification.status = cached_status
when :reblog
notification.status.reblog = cached_status
when :favourite
notification.favourite.status = cached_status
when :mention
notification.mention.status = cached_status
when :poll
notification.poll.status = cached_status
end
end
notifications
end
end
after_initialize :set_from_account
before_validation :set_from_account
private
def set_from_account
return unless new_record?
case activity_type
when 'Status', 'Follow', 'Favourite', 'FollowRequest', 'Poll', 'Report'
self.from_account_id = activity&.account_id
when 'Mention'
self.from_account_id = activity&.status&.account_id
when 'Account'
self.from_account_id = activity&.id
end
end
end
|