• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ruby-grape / grape / 22995548587

12 Mar 2026 09:37AM UTC coverage: 96.764% (-0.08%) from 96.843%
22995548587

Pull #2657

github

ericproulx
Pass attrs directly to AttributesIterator instead of validator

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Pull Request #2657: Instantiate validators at definition time

1078 of 1170 branches covered (92.14%)

Branch coverage included in aggregate %.

149 of 151 new or added lines in 18 files covered. (98.68%)

6 existing lines in 2 files now uncovered.

3377 of 3434 relevant lines covered (98.34%)

33309.81 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.67
/lib/grape/validations/validators/base.rb
1
# frozen_string_literal: true
2

3
module Grape
37✔
4
  module Validations
37✔
5
    module Validators
37✔
6
      # Base class for all parameter validators.
7
      #
8
      # == Freeze contract
9
      # Validator instances are shared across requests and are frozen after
10
      # initialization (via +.new+). Subclasses must set all state in
11
      # +initialize+ — lazy ivar assignment (e.g. +memoize+, <tt>||=</tt>)
12
      # will raise +FrozenError+ at request time. If a specific ivar must
13
      # remain mutable (e.g. a coercer with internal lazy state), override
14
      # +freeze_state!+ and skip that ivar.
15
      class Base
37✔
16
        include Grape::Util::Translation
37✔
17

18
        attr_reader :attrs
37✔
19

20
        # Creates a new Validator from options specified
21
        # by a +requires+ or +optional+ directive during
22
        # parameter definition.
23
        # @param attrs [Array] names of attributes to which the Validator applies
24
        # @param options [Object] implementation-dependent Validator options
25
        # @param required [Boolean] attribute(s) are required or optional
26
        # @param scope [ParamsScope] parent scope for this Validator
27
        # @param opts [Hash] additional validation options
28
        def initialize(attrs, options, required, scope, opts)
37✔
29
          @attrs = Array(attrs)
115,403✔
30
          @option = options
115,403✔
31
          @required = required
115,403✔
32
          @scope = scope
115,403✔
33
          @fail_fast, @allow_blank = opts.values_at(:fail_fast, :allow_blank)
115,403✔
34
        end
35

36
        # Validates a given request.
37
        # @note Override #validate! unless you need to access the entire request.
38
        # @param request [Grape::Request] the request currently being handled
39
        # @raise [Grape::Exceptions::Validation] if validation failed
40
        # @return [void]
41
        def validate(request)
37✔
42
          return unless @scope.should_validate?(request.params)
112,843✔
43

44
          validate!(request.params)
93,251✔
45
        end
46

47
        def self.new(...)
37✔
48
          # freeze_state! is private; __send__ is required because class methods
49
          # cannot call private instance methods with an explicit receiver.
50
          super.tap { |instance| instance.__send__(:freeze_state!) }.freeze
230,582✔
51
        end
52

53
        def self.inherited(klass)
37✔
54
          super
1,515✔
55
          Validations.register(klass)
1,515✔
56
        end
57

58
        def fail_fast?
37✔
59
          @fail_fast
14,049✔
60
        end
61

62
        # Validates a given parameter hash.
63
        # @note Override #validate_param! for per-parameter validation,
64
        #   or #validate if you need access to the entire request.
65
        # @param params [Hash] parameters to validate
66
        # @raise [Grape::Exceptions::Validation] if validation failed
67
        # @return [void]
68
        def validate!(params)
37✔
69
          attributes = SingleAttributeIterator.new(@attrs, @scope, params)
85,890✔
70
          # we collect errors inside array because
71
          # there may be more than one error per field
72
          array_errors = []
85,890✔
73

74
          attributes.each do |val, attr_name, empty_val|
85,890✔
75
            next if !@scope.required? && empty_val
93,634!
76
            next unless @scope.meets_dependency?(val, params)
93,634✔
77

78
            validate_param!(attr_name, val) if @required || (hash_like?(val) && val.key?(attr_name))
93,346✔
79
          rescue Grape::Exceptions::Validation => e
80
            array_errors << e
12,481✔
81
          end
82

83
          raise Grape::Exceptions::ValidationArrayErrors.new(array_errors) if array_errors.any?
85,890✔
84
        end
85

86
        protected
37✔
87

88
        # Validates a single attribute. Override in subclasses.
89
        # @param attr_name [Symbol, String] the attribute name
90
        # @param params [Hash] the parameter hash containing the attribute
91
        # @raise [Grape::Exceptions::Validation] if validation failed
92
        # @return [void]
93
        def validate_param!(attr_name, params)
37✔
NEW
94
          raise NotImplementedError
×
95
        end
96

97
        private
37✔
98

99
        # Deep-freezes all instance variables set during +initialize+.
100
        # Called by +.new+ before the instance itself is frozen.
101
        # Override in subclasses to skip ivars that intentionally hold
102
        # mutable state (e.g. a coercer with internal lazy memoization):
103
        #
104
        #   def freeze_state!
105
        #     super
106
        #     # @my_mutable_ivar is intentionally left unfrozen
107
        #   end
108
        def freeze_state!
37✔
109
          instance_variables.each do |ivar|
115,179✔
110
            Grape::Util::DeepFreeze.deep_freeze(instance_variable_get(ivar))
877,616✔
111
          end
112
        end
113

114
        def hash_like?(obj)
37✔
115
          obj.respond_to?(:key?)
280,922✔
116
        end
117

118
        def options_key?(key, options = nil)
37✔
119
          current_options = options || @option
126,058✔
120
          hash_like?(current_options) && current_options.key?(key) && !current_options[key].nil?
126,058✔
121
        end
122

123
        # Returns the effective message for a validation error.
124
        # Prefers an explicit +:message+ option, then +default_key+.
125
        # If both are nil, the block (if given) is called to compute a fallback —
126
        # useful for validators that build a message Hash for deferred i18n interpolation.
127
        # @example
128
        #   @exception_message = message(:presence)             # symbol key or custom message
129
        #   @exception_message = message { build_hash_message } # computed fallback
130
        def message(default_key = nil)
37✔
131
          key = options_key?(:message) ? @option[:message] : default_key
109,226✔
132
          return key unless key.nil?
109,226!
133

NEW
UNCOV
134
          yield if block_given?
×
135
        end
136

137
        def option_value
37✔
138
          options_key?(:value) ? @option[:value] : @option
16,160✔
139
        end
140

141
        def scrub(value)
37✔
142
          return value unless value.respond_to?(:valid_encoding?) && !value.valid_encoding?
10,560✔
143

144
          value.scrub
96✔
145
        end
146
      end
147
    end
148
  end
149
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc