Commit ee2dc64b authored by pbair's avatar pbair

Cop to disallow methods that use subtransactions

Add a new cop that disallows common methods that use subtransactions
within their implementation: safe_ensure_unique and create_or_find_by
variants.
parent 13646226
...@@ -717,3 +717,8 @@ Performance/ActiveRecordSubtransactions: ...@@ -717,3 +717,8 @@ Performance/ActiveRecordSubtransactions:
Exclude: Exclude:
- 'spec/**/*.rb' - 'spec/**/*.rb'
- 'ee/spec/**/*.rb' - 'ee/spec/**/*.rb'
Performance/ActiveRecordSubtransactionMethods:
Exclude:
- 'spec/**/*.rb'
- 'ee/spec/**/*.rb'
...@@ -38,7 +38,7 @@ class Clusters::ClustersController < Clusters::BaseController ...@@ -38,7 +38,7 @@ class Clusters::ClustersController < Clusters::BaseController
def new def new
if params[:provider] == 'aws' if params[:provider] == 'aws'
@aws_role = Aws::Role.create_or_find_by!(user: current_user) @aws_role = Aws::Role.create_or_find_by!(user: current_user) # rubocop:disable Performance/ActiveRecordSubtransactionMethods
@instance_types = load_instance_types.to_json @instance_types = load_instance_types.to_json
elsif params[:provider] == 'gcp' elsif params[:provider] == 'gcp'
......
...@@ -66,7 +66,7 @@ class ApplicationRecord < ActiveRecord::Base ...@@ -66,7 +66,7 @@ class ApplicationRecord < ActiveRecord::Base
def self.safe_find_or_create_by(*args, &block) def self.safe_find_or_create_by(*args, &block)
return optimized_safe_find_or_create_by(*args, &block) if Feature.enabled?(:optimize_safe_find_or_create_by, default_enabled: :yaml) return optimized_safe_find_or_create_by(*args, &block) if Feature.enabled?(:optimize_safe_find_or_create_by, default_enabled: :yaml)
safe_ensure_unique(retries: 1) do safe_ensure_unique(retries: 1) do # rubocop:disable Performance/ActiveRecordSubtransactionMethods
find_or_create_by(*args, &block) find_or_create_by(*args, &block)
end end
end end
......
...@@ -171,7 +171,7 @@ module CacheMarkdownField ...@@ -171,7 +171,7 @@ module CacheMarkdownField
# One retry is enough as next time `model_user_mention` should return the existing mention record, # One retry is enough as next time `model_user_mention` should return the existing mention record,
# that threw the `ActiveRecord::RecordNotUnique` exception in first place. # that threw the `ActiveRecord::RecordNotUnique` exception in first place.
self.class.safe_ensure_unique(retries: 1) do self.class.safe_ensure_unique(retries: 1) do # rubocop:disable Performance/ActiveRecordSubtransactionMethods
user_mention = model_user_mention user_mention = model_user_mention
# this may happen due to notes polymorphism, so noteable_id may point to a record # this may happen due to notes polymorphism, so noteable_id may point to a record
......
...@@ -89,7 +89,7 @@ class ExternalPullRequest < ApplicationRecord ...@@ -89,7 +89,7 @@ class ExternalPullRequest < ApplicationRecord
end end
def self.safe_find_or_initialize_and_update(find:, update:) def self.safe_find_or_initialize_and_update(find:, update:)
safe_ensure_unique(retries: 1) do safe_ensure_unique(retries: 1) do # rubocop:disable Performance/ActiveRecordSubtransactionMethods
model = find_or_initialize_by(find) model = find_or_initialize_by(find)
if model.update(update) if model.update(update)
......
...@@ -35,7 +35,7 @@ module MergeRequests ...@@ -35,7 +35,7 @@ module MergeRequests
end end
def save_approval(approval) def save_approval(approval)
Approval.safe_ensure_unique do Approval.safe_ensure_unique do # rubocop:disable Performance/ActiveRecordSubtransactionMethods
approval.save approval.save
end end
end end
......
...@@ -70,7 +70,7 @@ module Terraform ...@@ -70,7 +70,7 @@ module Terraform
return find_state!(find_params) if find_only return find_state!(find_params) if find_only
state = Terraform::State.create_or_find_by(find_params) state = Terraform::State.create_or_find_by(find_params) # rubocop:disable Performance/ActiveRecordSubtransactionMethods
# https://github.com/rails/rails/issues/36027 # https://github.com/rails/rails/issues/36027
return state unless state.errors.of_kind? :name, :taken return state unless state.errors.of_kind? :name, :taken
......
...@@ -59,7 +59,7 @@ module EE ...@@ -59,7 +59,7 @@ module EE
end end
def self.safe_find_or_create_by(*args) def self.safe_find_or_create_by(*args)
safe_ensure_unique(retries: 1) do safe_ensure_unique(retries: 1) do # rubocop:disable Performance/ActiveRecordSubtransactionMethods
find_or_create_by(*args) find_or_create_by(*args)
end end
end end
......
# frozen_string_literal: true
module RuboCop
module Cop
module Performance
# Cop that disallows certain methods that rely on subtransactions in their implementation.
# Companion to Performance/ActiveRecordSubtransactions, which bans direct usage of subtransactions.
class ActiveRecordSubtransactionMethods < RuboCop::Cop::Cop
MSG = 'Methods that rely on subtransactions should not be used. ' \
'For more information see: https://gitlab.com/gitlab-org/gitlab/-/issues/338346'
DISALLOWED_METHODS = %i[
safe_ensure_unique
create_or_find_by
create_or_find_by!
].freeze
def on_send(node)
return unless DISALLOWED_METHODS.include?(node.method_name)
add_offense(node, location: :selector)
end
end
end
end
end
# frozen_string_literal: true
require 'fast_spec_helper'
require_relative '../../../../rubocop/cop/performance/active_record_subtransaction_methods'
RSpec.describe RuboCop::Cop::Performance::ActiveRecordSubtransactionMethods do
subject(:cop) { described_class.new }
let(:message) { described_class::MSG }
shared_examples 'a method that uses a subtransaction' do |method_name|
it 'registers an offense' do
expect_offense(<<~RUBY)
Project.#{method_name}
#{'^' * method_name.length} #{message}
RUBY
end
end
context 'when the method uses a subtransaction' do
described_class::DISALLOWED_METHODS.each do |method|
it_behaves_like 'a method that uses a subtransaction', method
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment