Class: Gitlab::Utils::DelegatorOverride::Validator

Inherits:
Object
  • Object
show all
Defined in:
lib/gitlab/utils/delegator_override/validator.rb

Constant Summary collapse

UnexpectedDelegatorOverrideError =
Class.new(StandardError)
OVERRIDE_ERROR_MESSAGE =
<<~EOS
  We've detected that the delegator is overriding a specific method(s) on the target class.
  Please make sure if it's intentional and handle this error accordingly.
  See https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/presenters/README.md#validate-accidental-overrides for more information.
EOS

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(delegator_class) ⇒ Validator

Returns a new instance of Validator.


17
18
19
20
# File 'lib/gitlab/utils/delegator_override/validator.rb', line 17

def initialize(delegator_class)
  @delegator_class = delegator_class
  @target_classes = []
end

Instance Attribute Details

#delegator_classObject (readonly)

Returns the value of attribute delegator_class.


9
10
11
# File 'lib/gitlab/utils/delegator_override/validator.rb', line 9

def delegator_class
  @delegator_class
end

#target_classesObject (readonly)

Returns the value of attribute target_classes.


9
10
11
# File 'lib/gitlab/utils/delegator_override/validator.rb', line 9

def target_classes
  @target_classes
end

Instance Method Details

#add_allowlist(names) ⇒ Object


22
23
24
# File 'lib/gitlab/utils/delegator_override/validator.rb', line 22

def add_allowlist(names)
  allowed_method_names.concat(names)
end

#add_target(target_class) ⇒ Object


30
31
32
33
34
35
36
37
38
# File 'lib/gitlab/utils/delegator_override/validator.rb', line 30

def add_target(target_class)
  return unless target_class

  @target_classes << target_class

  # Also include all descendants inheriting from the target,
  # to make sure we catch methods that are only defined in some of them.
  @target_classes += target_class.descendants
end

#allowed_method_namesObject


26
27
28
# File 'lib/gitlab/utils/delegator_override/validator.rb', line 26

def allowed_method_names
  @allowed_method_names ||= []
end

#expand_on_ancestors(validators) ⇒ Object

This will make sure allowlist we put into ancestors are all included


41
42
43
44
45
46
47
48
49
50
51
# File 'lib/gitlab/utils/delegator_override/validator.rb', line 41

def expand_on_ancestors(validators)
  delegator_class.ancestors.each do |ancestor|
    next if delegator_class == ancestor # ancestor includes itself

    validator_ancestor = validators[ancestor]

    next unless validator_ancestor

    add_allowlist(validator_ancestor.allowed_method_names)
  end
end

#validate_overrides!Object


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
# File 'lib/gitlab/utils/delegator_override/validator.rb', line 53

def validate_overrides!
  return if target_classes.empty?

  errors = []

  # Workaround to fully load the instance methods in the target class.
  # See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/69823#note_678887402
  begin
    target_classes.map(&:new)
  rescue ArgumentError
    # Some models might raise ArgumentError here, but it's fine in this case,
    # because this is enough to force ActiveRecord to generate the methods we
    # need to verify, so it's safe to ignore it.
  end

  (delegator_class.instance_methods - allowlist).each do |method_name|
    target_classes.each do |target_class|
      next unless target_class.instance_methods.include?(method_name)

      errors << generate_error(method_name, target_class, delegator_class)
    end
  end

  return if errors.empty?

  details = errors.map { |error| "- #{error}" }.join("\n")

  raise UnexpectedDelegatorOverrideError,
    <<~TEXT
      #{OVERRIDE_ERROR_MESSAGE}
      Here are the conflict details.

      #{details}
    TEXT
end