Welcome to mirror list, hosted at ThFree Co, Russian Federation.

application_record.rb « models « app - gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 198a3653cd353ed209d1e42dd2a11426621e65a8 (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
# frozen_string_literal: true

class ApplicationRecord < ActiveRecord::Base
  include DatabaseReflection
  include Transactions
  include LegacyBulkInsert
  include CrossDatabaseModification
  include SensitiveSerializableHash

  self.abstract_class = true

  # We should avoid using pluck https://docs.gitlab.com/ee/development/sql.html#plucking-ids
  # but, if we are going to use it, let's try and limit the number of records
  MAX_PLUCK = 1_000

  alias_method :reset, :reload

  def self.without_order
    reorder(nil)
  end

  def self.id_in(ids)
    where(id: ids)
  end

  def self.primary_key_in(values)
    where(primary_key => values)
  end

  def self.iid_in(iids)
    where(iid: iids)
  end

  def self.id_not_in(ids)
    where.not(id: ids)
  end

  def self.pluck_primary_key
    where(nil).pluck(self.primary_key)
  end

  def self.safe_ensure_unique(retries: 0)
    transaction(requires_new: true) do # rubocop:disable Performance/ActiveRecordSubtransactions
      yield
    end
  rescue ActiveRecord::RecordNotUnique
    if retries > 0
      retries -= 1
      retry
    end

    false
  end

  def self.safe_find_or_create_by!(*args, &block)
    safe_find_or_create_by(*args, &block).tap do |record|
      raise ActiveRecord::RecordNotFound unless record.present?

      record.validate! unless record.persisted?
    end
  end

  # Start a new transaction with a shorter-than-usual statement timeout. This is
  # currently one third of the default 15-second timeout with a 500ms buffer
  # to allow callers gracefully handling the errors to still complete within
  # the 5s target duration of a low urgency request.
  def self.with_fast_read_statement_timeout(timeout_ms = 4500)
    ::Gitlab::Database::LoadBalancing::Session.current.fallback_to_replicas_for_ambiguous_queries do
      transaction(requires_new: true) do # rubocop:disable Performance/ActiveRecordSubtransactions
        connection.exec_query("SET LOCAL statement_timeout = #{timeout_ms}")

        yield
      end
    end
  end

  def self.safe_find_or_create_by(*args, &block)
    record = find_by(*args)
    return record if record.present?

    # We need to use `all.create` to make this implementation follow `find_or_create_by` which delegates this in
    # https://github.com/rails/rails/blob/v6.1.3.2/activerecord/lib/active_record/querying.rb#L22
    #
    # When calling this method on an association, just calling `self.create` would call `ActiveRecord::Persistence.create`
    # and that skips some code that adds the newly created record to the association.
    transaction(requires_new: true) { all.create(*args, &block) } # rubocop:disable Performance/ActiveRecordSubtransactions
  rescue ActiveRecord::RecordNotUnique
    find_by(*args)
  end

  def create_or_load_association(association_name)
    association(association_name).create unless association(association_name).loaded?
  rescue ActiveRecord::RecordNotUnique, PG::UniqueViolation
    association(association_name).reader
  end

  def self.underscore
    Gitlab::SafeRequestStore.fetch("model:#{self}:underscore") { self.to_s.underscore }
  end

  def self.where_exists(query)
    where('EXISTS (?)', query.select(1))
  end

  def self.where_not_exists(query)
    where('NOT EXISTS (?)', query.select(1))
  end

  def self.declarative_enum(enum_mod)
    enum(enum_mod.key => enum_mod.values)
  end

  def self.cached_column_list
    self.column_names.map { |column_name| self.arel_table[column_name] }
  end

  def self.default_select_columns
    if ignored_columns.any?
      cached_column_list
    else
      arel_table[Arel.star]
    end
  end

  def readable_by?(user)
    Ability.allowed?(user, "read_#{to_ability_name}".to_sym, self)
  end

  def to_ability_name
    model_name.element
  end
end