Class: MergeRequest::DiffCommitUser

Inherits:
ApplicationRecord show all
Defined in:
app/models/merge_request/diff_commit_user.rb

Constant Summary

Constants inherited from ApplicationRecord

ApplicationRecord::MAX_PLUCK

Class Method Summary collapse

Methods inherited from ApplicationRecord

cached_column_list, #create_or_load_association, declarative_enum, default_select_columns, id_in, id_not_in, iid_in, pluck_primary_key, primary_key_in, #readable_by?, safe_ensure_unique, safe_find_or_create_by, safe_find_or_create_by!, #to_ability_name, underscore, where_exists, where_not_exists, with_fast_read_statement_timeout, without_order

Methods included from SensitiveSerializableHash

#serializable_hash

Class Method Details

.bulk_find(pairs) ⇒ Object

Finds many (name, email) pairs in bulk.


30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'app/models/merge_request/diff_commit_user.rb', line 30

def self.bulk_find(pairs)
  queries = {}
  rows = []

  pairs.each do |(name, email)|
    queries[[name, email]] = where(name: name, email: email).to_sql
  end

  # We may end up having to query many users. To ensure we don't hit any
  # query size limits, we get a fixed number of users at a time.
  queries.values.each_slice(1_000).map do |slice|
    rows.concat(from("(#{slice.join("\nUNION ALL\n")}) #{table_name}").to_a)
  end

  rows
end

.bulk_find_or_create(pairs) ⇒ Object

Finds or creates rows for the given pairs of names and Emails.

The `names_and_emails` argument must be an Array/Set of tuples like so:

[
  [name, email],
  [name, email],
  ...
]

This method expects that the names and Emails have already been trimmed to at most 512 characters.

The return value is a Hash that maps these tuples to instances of this model.


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
# File 'app/models/merge_request/diff_commit_user.rb', line 62

def self.bulk_find_or_create(pairs)
  mapping = {}
  create = []

  # Over time, fewer new rows need to be created. We take advantage of that
  # here by first finding all rows that already exist, using a limited number
  # of queries (in most cases only one query will be needed).
  bulk_find(pairs).each do |row|
    mapping[[row.name, row.email]] = row
  end

  pairs.each do |(name, email)|
    create << { name: name, email: email } unless mapping[[name, email]]
  end

  return mapping if create.empty?

  # Sometimes we may need to insert new users into the table. We do this in
  # bulk, so we only need one INSERT for all missing users.
  insert_all(create, returning: %w[id name email]).each do |row|
    mapping[[row['name'], row['email']]] =
      new(id: row['id'], name: row['name'], email: row['email'])
  end

  # It's possible for (name, email) pairs to be inserted concurrently,
  # resulting in the above insert not returning anything. Here we get any
  # remaining users that were created concurrently.
  bulk_find(pairs.reject { |pair| mapping.key?(pair) }).each do |row|
    mapping[[row.name, row.email]] = row
  end

  mapping
end

.find_or_create(name, email) ⇒ Object

Creates a new row, or returns an existing one if a row already exists.


23
24
25
26
27
# File 'app/models/merge_request/diff_commit_user.rb', line 23

def self.find_or_create(name, email)
  find_or_create_by!(name: name, email: email)
rescue ActiveRecord::RecordNotUnique
  retry
end

.prepare(value) ⇒ Object

Prepares a value to be inserted into a column in the table `merge_request_diff_commit_users`. Values in this table are limited to 512 characters.

We treat empty strings as NULL values, as there's no point in (for example) storing a row where both the name and Email are an empty string. In addition, if we treated them differently we could end up with two rows: one where field X is NULL, and one where field X is an empty string. This is redundant, so we avoid storing such data.


18
19
20
# File 'app/models/merge_request/diff_commit_user.rb', line 18

def self.prepare(value)
  value.present? ? value[0..511] : nil
end