summaryrefslogtreecommitdiff
path: root/app/models/concerns/awardable.rb
blob: 14bc56f0eeec48b644a1db18f50807efebd7bec7 (plain)
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
# frozen_string_literal: true

module Awardable
  extend ActiveSupport::Concern

  included do
    has_many :award_emoji, -> { includes(:user).order(:id) }, as: :awardable, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent

    if self < Participable
      # By default we always load award_emoji user association
      participant :award_emoji
    end
  end

  class_methods do
    def awarded(user, name = nil)
      sql = <<~EOL
        EXISTS (
          SELECT TRUE
          FROM award_emoji
          WHERE user_id = :user_id AND
                #{"name = :name AND" if name.present?}
                awardable_type = :awardable_type AND
                awardable_id = #{self.arel_table.name}.id
        )
      EOL

      where(sql, user_id: user.id, name: name, awardable_type: self.name)
    end

    def not_awarded(user)
      sql = <<~EOL
        NOT EXISTS (
          SELECT TRUE
          FROM award_emoji
          WHERE user_id = :user_id AND
                awardable_type = :awardable_type AND
                awardable_id = #{self.arel_table.name}.id
        )
      EOL

      where(sql, user_id: user.id, awardable_type: self.name)
    end

    def order_upvotes_desc
      order_votes(AwardEmoji::UPVOTE_NAME, 'DESC')
    end

    def order_upvotes_asc
      order_votes(AwardEmoji::UPVOTE_NAME, 'ASC')
    end

    def order_downvotes_desc
      order_votes(AwardEmoji::DOWNVOTE_NAME, 'DESC')
    end

    # Order votes by emoji, optional sort order param `descending` defaults to true
    def order_votes(emoji_name, direction)
      awardable_table = self.arel_table
      awards_table = AwardEmoji.arel_table

      join_clause = awardable_table.join(awards_table, Arel::Nodes::OuterJoin).on(
        awards_table[:awardable_id].eq(awardable_table[:id]).and(
          awards_table[:awardable_type].eq(self.name).and(
            awards_table[:name].eq(emoji_name)
          )
        )
      ).join_sources

      joins(join_clause).group(awardable_table[:id]).reorder("COUNT(award_emoji.id) #{direction}")
    end
  end

  def grouped_awards(with_thumbs: true)
    # By default we always load award_emoji user association
    awards = award_emoji.group_by(&:name)

    if with_thumbs
      awards[AwardEmoji::UPVOTE_NAME]   ||= []
      awards[AwardEmoji::DOWNVOTE_NAME] ||= []
    end

    awards
  end

  def downvotes
    award_emoji.downvotes.count
  end

  def upvotes
    award_emoji.upvotes.count
  end

  def emoji_awardable?
    true
  end

  def user_can_award?(current_user)
    Ability.allowed?(current_user, :award_emoji, self)
  end

  def user_authored?(current_user)
    author = self.respond_to?(:author) ? self.author : self.user

    author == current_user
  end

  def awarded_emoji?(emoji_name, current_user)
    award_emoji.where(name: emoji_name, user: current_user).exists?
  end

  def create_award_emoji(name, current_user)
    return unless emoji_awardable?

    award_emoji.create(name: normalize_name(name), user: current_user)
  end

  def remove_award_emoji(name, current_user)
    award_emoji.where(name: name, user: current_user).destroy_all # rubocop: disable DestroyAll
  end

  def toggle_award_emoji(emoji_name, current_user)
    if awarded_emoji?(emoji_name, current_user)
      remove_award_emoji(emoji_name, current_user)
    else
      create_award_emoji(emoji_name, current_user)
    end
  end

  private

  def normalize_name(name)
    Gitlab::Emoji.normalize_emoji_name(name)
  end
end