summaryrefslogtreecommitdiff
path: root/app/models/user_interacted_project.rb
blob: ae6778e49be261291561308597fc95751ca3e854 (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
# frozen_string_literal: true

class UserInteractedProject < ActiveRecord::Base
  belongs_to :user
  belongs_to :project

  validates :project_id, presence: true
  validates :user_id, presence: true

  CACHE_EXPIRY_TIME = 1.day

  # Schema version required for this model
  REQUIRED_SCHEMA_VERSION = 20180223120443

  class << self
    def track(event)
      # For events without a project, we simply don't care.
      # An example of this is the creation of a snippet (which
      # is not related to any project).
      return unless event.project_id

      attributes = {
        project_id: event.project_id,
        user_id: event.author_id
      }

      cached_exists?(attributes) do
        transaction(requires_new: true) do
          begin
            where(attributes).select(1).first || create!(attributes)
            true # not caching the whole record here for now
          rescue ActiveRecord::RecordNotUnique
            # Note, above queries are not atomic and prone
            # to race conditions (similar like #find_or_create!).
            # In the case where we hit this, the record we want
            # already exists - shortcut and return.
            true
          end
        end
      end
    end

    # Check if we can safely call .track (table exists)
    def available?
      @available_flag ||= ActiveRecord::Migrator.current_version >= REQUIRED_SCHEMA_VERSION # rubocop:disable Gitlab/PredicateMemoization
    end

    # Flushes cached information about schema
    def reset_column_information
      @available_flag = nil
      super
    end

    private

    def cached_exists?(project_id:, user_id:, &block)
      cache_key = "user_interacted_projects:#{project_id}:#{user_id}"
      Rails.cache.fetch(cache_key, expires_in: CACHE_EXPIRY_TIME, &block)
    end
  end
end