Class: PostAction

Inherits:
ActiveRecord::Base
  • Object
show all
Includes:
RateLimiter::OnCreateRecord, Trashable
Defined in:
app/models/post_action.rb

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Trashable

#recover!, #trash!, #trashed?

Methods included from RateLimiter::OnCreateRecord

#default_rate_limiter, #disable_rate_limits!, included

Class Method Details

.copy(original_post, target_post) ⇒ Object



126
127
128
129
130
131
132
133
134
135
136
137
# File 'app/models/post_action.rb', line 126

def self.copy(original_post, target_post)
  cols_to_copy = (column_names - %w[id post_id]).join(", ")

  DB.exec <<~SQL
  INSERT INTO post_actions(post_id, #{cols_to_copy})
  SELECT #{target_post.id}, #{cols_to_copy}
  FROM post_actions
  WHERE post_id = #{original_post.id}
  SQL

  target_post.post_actions.each { |post_action| post_action.update_counters }
end

.count_per_day_for_type(post_action_type, opts = nil) ⇒ Object



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
# File 'app/models/post_action.rb', line 65

def self.count_per_day_for_type(post_action_type, opts = nil)
  opts ||= {}
  result = unscoped.where(post_action_type_id: post_action_type)
  result =
    result.where(
      "post_actions.created_at >= ?",
      opts[:start_date] || (opts[:since_days_ago] || 30).days.ago,
    )
  result = result.where("post_actions.created_at <= ?", opts[:end_date]) if opts[:end_date]
  if opts[:category_id]
    if opts[:include_subcategories]
      result =
        result.joins(post: :topic).where(
          "topics.category_id IN (?)",
          Category.subcategory_ids(opts[:category_id]),
        )
    else
      result = result.joins(post: :topic).where("topics.category_id = ?", opts[:category_id])
    end
  end

  if opts[:group_ids]
    result =
      result
        .joins("INNER JOIN users ON users.id = post_actions.user_id")
        .joins("INNER JOIN group_users ON group_users.user_id = users.id")
        .where("group_users.group_id IN (?)", opts[:group_ids])
  end

  result.group("date(post_actions.created_at)").order("date(post_actions.created_at)").count
end

.counts_for(collection, user) ⇒ Object



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'app/models/post_action.rb', line 23

def self.counts_for(collection, user)
  return {} if collection.blank? || !user

  collection_ids = collection.map(&:id)
  user_id = user.try(:id) || 0

  post_actions = PostAction.where(post_id: collection_ids, user_id: user_id)

   = {}
  post_actions.each do |post_action|
    [post_action.post_id] ||= {}
    [post_action.post_id][post_action.post_action_type_id] = post_action
  end

  
end

.limit_action!(user, post, post_action_type_id) ⇒ Object



122
123
124
# File 'app/models/post_action.rb', line 122

def self.limit_action!(user, post, post_action_type_id)
  RateLimiter.new(user, "post_action-#{post.id}_#{post_action_type_id}", 4, 1.minute).performed!
end

.lookup_for(user, topics, post_action_type_id) ⇒ Object



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'app/models/post_action.rb', line 40

def self.lookup_for(user, topics, post_action_type_id)
  return if topics.blank?
  # in critical path 2x faster than AR
  #
  topic_ids = topics.map(&:id)
  map = {}

  builder = DB.build <<~SQL
    SELECT p.topic_id, p.post_number
    FROM post_actions pa
    JOIN posts p ON pa.post_id = p.id
    WHERE p.deleted_at IS NULL AND pa.deleted_at IS NULL AND
       pa.post_action_type_id = :post_action_type_id AND
       pa.user_id = :user_id AND
       p.topic_id IN (:topic_ids)
    ORDER BY p.topic_id, p.post_number
  SQL

  builder
    .query(user_id: user.id, post_action_type_id: post_action_type_id, topic_ids: topic_ids)
    .each { |row| (map[row.topic_id] ||= []) << row.post_number }

  map
end

Instance Method Details

#add_moderator_post_if_needed(moderator, disposition, delete_post = false) ⇒ Object



97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'app/models/post_action.rb', line 97

def add_moderator_post_if_needed(moderator, disposition, delete_post = false)
  return if !SiteSetting.auto_respond_to_flag_actions
  return if related_post.nil? || related_post.topic.nil?
  return if staff_already_replied?(related_post.topic)
  message_key = +"flags_dispositions.#{disposition}"
  message_key << "_and_deleted" if delete_post

  I18n.with_locale(SiteSetting.default_locale) do
    related_post.topic.add_moderator_post(moderator, I18n.t(message_key))
  end

  # archive message for moderators
  GroupArchivedMessage.archive!(Group[:moderators].id, related_post.topic)
end

#ensure_unique_actionsObject



188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
# File 'app/models/post_action.rb', line 188

def ensure_unique_actions
  post_action_type_ids =
    is_flag? ? post_action_type_view.notify_flag_types.values : post_action_type_id

  acted =
    PostAction
      .where(user_id: user_id)
      .where(post_id: post_id)
      .where(post_action_type_id: post_action_type_ids)
      .where(deleted_at: nil)
      .where(disagreed_at: nil)
      .where(targets_topic: targets_topic)
      .exists?

  errors.add(:post_action_type_id) if acted
end

#is_flag?Boolean

Returns:

  • (Boolean)


155
156
157
# File 'app/models/post_action.rb', line 155

def is_flag?
  !!post_action_type_view.notify_flag_types[post_action_type_id]
end

#is_like?Boolean

Returns:

  • (Boolean)


151
152
153
# File 'app/models/post_action.rb', line 151

def is_like?
  post_action_type_id == post_action_type_view.types[:like]
end

#is_private_message?Boolean

Returns:

  • (Boolean)


159
160
161
162
# File 'app/models/post_action.rb', line 159

def is_private_message?
  post_action_type_id == post_action_type_view.types[:notify_user] ||
    post_action_type_id == post_action_type_view.types[:notify_moderators]
end

#post_action_rate_limiterObject

A custom rate limiter for this model



165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
# File 'app/models/post_action.rb', line 165

def post_action_rate_limiter
  return unless is_flag? || is_like?

  return @rate_limiter if @rate_limiter.present?

  %w[like flag].each do |type|
    if public_send("is_#{type}?")
      limit = SiteSetting.get("max_#{type}s_per_day")

      if (is_flag? || is_like?) && user && user.trust_level >= 2
        multiplier =
          SiteSetting.get("tl#{user.trust_level}_additional_#{type}s_per_day_multiplier").to_f
        multiplier = 1.0 if multiplier < 1.0

        limit = (limit * multiplier).to_i
      end

      @rate_limiter = RateLimiter.new(user, "create_#{type}", limit, 1.day.to_i)
      return @rate_limiter
    end
  end
end

#post_action_type_keyObject



205
206
207
# File 'app/models/post_action.rb', line 205

def post_action_type_key
  post_action_type_view.types[post_action_type_id]
end

#post_action_type_viewObject



147
148
149
# File 'app/models/post_action.rb', line 147

def post_action_type_view
  @post_action_type_view ||= PostActionTypeView.new
end

#remove_act!(user) ⇒ Object



139
140
141
142
143
144
145
# File 'app/models/post_action.rb', line 139

def remove_act!(user)
  trash!(user)
  # NOTE: save is called to ensure all callbacks are called
  # trash will not trigger callbacks, and triggering after_commit
  # is not trivial
  save
end

#staff_already_replied?(topic) ⇒ Boolean

Returns:

  • (Boolean)


112
113
114
115
116
117
118
119
120
# File 'app/models/post_action.rb', line 112

def staff_already_replied?(topic)
  topic
    .posts
    .where(
      "user_id IN (SELECT id FROM users WHERE moderator OR admin) OR (post_type != :regular_post_type)",
      regular_post_type: Post.types[:regular],
    )
    .exists?
end

#update_countersObject



209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
# File 'app/models/post_action.rb', line 209

def update_counters
  # Update denormalized counts
  column = "#{post_action_type_key}_count"
  count = PostAction.where(post_id: post_id).where(post_action_type_id: post_action_type_id).count

  # We probably want to refactor this method to something cleaner.
  case post_action_type_key
  when :like
    # 'like_score' is weighted higher for staff accounts
    score =
      PostAction
        .joins(:user)
        .where(post_id: post_id)
        .sum(
          "CASE WHEN users.moderator OR users.admin THEN #{SiteSetting.staff_like_weight} ELSE 1 END",
        )
    Post.where(id: post_id).update_all [
                   "like_count = :count, like_score = :score",
                   count: count,
                   score: score,
                 ]
  else
    if ActiveRecord::Base.connection.column_exists?(:posts, column)
      Post.where(id: post_id).update_all ["#{column} = ?", count]
    end
  end

  topic_id = Post.with_deleted.where(id: post_id).pick(:topic_id)

  # topic_user
  if post_action_type_key == :like
    TopicUser.update_post_action_cache(
      user_id: user_id,
      topic_id: topic_id,
      post_action_type: post_action_type_key,
    )
  end

  Topic.find_by(id: topic_id)&.update_action_counts if column == "like_count"
end