Class: ProjectAuthorization

Inherits:
ApplicationRecord show all
Extended by:
SuppressCompositePrimaryKeyWarning
Includes:
EachBatch, FromUnion
Defined in:
app/models/project_authorization.rb

Constant Summary

Constants inherited from ApplicationRecord

ApplicationRecord::MAX_PLUCK

Constants included from HasCheckConstraints

HasCheckConstraints::NOT_NULL_CHECK_PATTERN

Constants included from ResetOnColumnErrors

ResetOnColumnErrors::MAX_RESET_PERIOD

Class Method Summary collapse

Methods inherited from ApplicationRecord

===, cached_column_list, #create_or_load_association, current_transaction, declarative_enum, default_select_columns, delete_all_returning, #deleted_from_database?, id_in, id_not_in, iid_in, nullable_column?, 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 ResetOnColumnErrors

#reset_on_union_error, #reset_on_unknown_attribute_error

Methods included from Gitlab::SensitiveSerializableHash

#serializable_hash

Class Method Details

.find_or_create_authorization_for(user_id, project_id, access_level) ⇒ Object



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

def self.find_or_create_authorization_for(user_id, project_id, access_level)
  # We only try to find the record by user and project so that we match the current model level validation and
  # database constraints.
  # Ideally, in the case where a record exists with a different access_level,
  # this will save us from performing an unnecessary upsert that will hit the `ON CONFLICT DO NOTHING` path.
  # Due to the nature of project authorizations, differences in access_level should be handled by the
  # recalculation service/workers and not anything that invokes this method.
  find_by(user_id: user_id, project_id: project_id) ||

    # If not, we try to create it with `upsert`.
    # We use upsert for these reasons:
    #    - No subtransactions
    #    - Due to the use of `on_duplicate: :skip`, we are essentially issuing a `ON CONFLICT DO NOTHING`.
    #       - Postgres will take care of skipping the record without errors if a similar record was created
    #         by then in another thread.
    #       - There is no explicit error being thrown because we said "ON CONFLICT DO NOTHING".
    #         With this we avoid both the problems with subtransactions that could arise when we upgrade Rails,
    #         see https://gitlab.com/gitlab-org/gitlab/-/issues/439567, and also with race conditions.

    upsert(
      { project_id: project_id, user_id: user_id, access_level: access_level, is_unique: true },
      unique_by: [:project_id, :user_id], # skip unique_by access_level here to avoid conflicting access.
      on_duplicate: :skip # Do not change access_level, could cause conflicting permissions.
    )
end

.insert_all(attributes) ⇒ Object

This method overrides its ActiveRecord’s version in order to work correctly with composite primary keys and fix the tests for Rails 6.1

Consider using BulkInsertSafe module instead since we plan to refactor it in gitlab.com/gitlab-org/gitlab/-/issues/331264



40
41
42
# File 'app/models/project_authorization.rb', line 40

def self.insert_all(attributes)
  super(attributes, unique_by: connection.schema_cache.primary_keys(table_name))
end

.select_from_union(relations) ⇒ Object



29
30
31
32
33
# File 'app/models/project_authorization.rb', line 29

def self.select_from_union(relations)
  from_union(relations)
    .select(['project_id', 'MAX(access_level) AS access_level'])
    .group(:project_id)
end