diff --git a/lib/grape/validations/validators/coerce.rb b/lib/grape/validations/validators/coerce.rb index a004119d77..4e7434d18e 100644 --- a/lib/grape/validations/validators/coerce.rb +++ b/lib/grape/validations/validators/coerce.rb @@ -7,7 +7,7 @@ module Validations class CoerceValidator < Base def validate_param!(attr_name, params) fail Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message_key: :coerce unless params.is_a? Hash - new_value = coerce_value(@option, params[attr_name]) + new_value = coerce_value(params[attr_name]) if valid_type?(new_value) params[attr_name] = new_value else @@ -50,20 +50,12 @@ def valid_type?(val) end end - def coerce_value(type, val) + def coerce_value(val) # Don't coerce things other than nil to Arrays or Hashes return val || [] if type == Array return val || Set.new if type == Set return val || {} if type == Hash - # To support custom types that Virtus can't easily coerce, pass in an - # explicit coercer. Custom types must implement a `parse` class method. - converter_options = {} - if ParameterTypes.custom_type?(type) - converter_options[:coercer] = type.method(:parse) - end - - converter = Virtus::Attribute.build(type, converter_options) converter.coerce(val) # not the prettiest but some invalid coercion can currently trigger @@ -71,6 +63,23 @@ def coerce_value(type, val) rescue InvalidValue.new end + + def type + @option + end + + def converter + @converter ||= + begin + # To support custom types that Virtus can't easily coerce, pass in an + # explicit coercer. Custom types must implement a `parse` class method. + converter_options = {} + if ParameterTypes.custom_type?(type) + converter_options[:coercer] = type.method(:parse) + end + Virtus::Attribute.build(type, converter_options) + end + end end end end diff --git a/spec/grape/validations/validators/coerce_spec.rb b/spec/grape/validations/validators/coerce_spec.rb index 391e58f593..d05df842ba 100644 --- a/spec/grape/validations/validators/coerce_spec.rb +++ b/spec/grape/validations/validators/coerce_spec.rb @@ -252,5 +252,18 @@ class User expect(last_response.body).to eq('Fixnum') end end + + context 'converter' do + it 'does not build Virtus::Attribute multiple times' do + subject.params do + requires :something, type: Array[String] + end + subject.get do + end + + expect(Virtus::Attribute).to receive(:build).at_most(2).times.and_call_original + 10.times { get '/' } + end + end end end