Skip to content

Instantly share code, notes, and snippets.

@jordaaash
Created May 14, 2014 17:22
Show Gist options
  • Save jordaaash/260662acf75e66d9bf79 to your computer and use it in GitHub Desktop.
Save jordaaash/260662acf75e66d9bf79 to your computer and use it in GitHub Desktop.
Grape: Use case for value coercion after or between running other validators
class ClampValidator < Grape::Validations::SingleOptionValidator
def validate_param!(attr_name, params)
params[attr_name] = clamp(params[attr_name], @option.min, @option.max)
end
def clamp (value, min, max)
[[value, max].min, min].max
end
end
class Companies < Grape::API
SEPARATOR_REGEXP = /,/
INTEGER_REGEXP = /\A(0|[1-9][0-9]*)\z/
ID_REGEXP = /\A([1-9][0-9]*)\z/
IDS_REGEXP = /\A([1-9][0-9]*)(,([1-9][0-9]*))*\z/
helpers do
params :filters do
# Example of coercing after RegExp validation for a non-zero Integer
optional :location,
regexp: ID_REGEXP,
convert: Integer
# Example of using coercion after RegExp validation and splitting a String into an Array
optional :services,
regexp: IDS_REGEXP,
split: SEPARATOR_REGEXP,
convert: Set[Integer]
end
params :pagination do
# Examples of using coercion after RegExp validation, but before clamping the value between a Range
optional :limit,
regexp: INTEGER_REGEXP,
convert: Integer,
clamp: [0..100],
default: 25
optional :offset,
regexp: INTEGER_REGEXP,
convert: Integer,
clamp: [0..-1],
default: 0
end
end
resource :companies do
params do
use :filters, :pagination
end
get do
location, services, offset, limit = params[:location], params[:services], params[:offset], params[:limit]
Company.where(location: location, services: services).offset(offset * limit).limit(limit).all
end
end
end
# Modified from https://github.com/intridea/grape/blob/master/lib/grape/validations/coerce.rb
# Set inclusion suggested at https://github.com/intridea/grape/pull/642
class ConvertValidator < Grape::Validations::SingleOptionValidator
def validate_param!(attr_name, params)
unless params.is_a? Hash
raise Grape::Exceptions::Validation,
param: @scope.full_name(attr_name), message_key: :convert
end
new_value = convert_value(@option, params[attr_name])
if valid_type?(new_value)
params[attr_name] = new_value
else
raise Grape::Exceptions::Validation,
param: @scope.full_name(attr_name), message_key: :convert
end
end
class InvalidValue
end
private
def _valid_array_type?(type, values)
values.all? do |val|
_valid_single_type?(type, val)
end
end
def _valid_single_type?(klass, val)
# allow nil, to ignore when a parameter is absent
return true if val.nil?
if klass == Virtus::Attribute::Boolean
val.is_a?(TrueClass) || val.is_a?(FalseClass)
elsif klass == Rack::Multipart::UploadedFile
val.is_a?(Hashie::Mash) && val.key?(:tempfile)
else
val.is_a?(klass)
end
end
def valid_type?(val)
if @option.is_a?(Array) || @option.is_a?(Set)
_valid_array_type?(@option.first, val)
else
_valid_single_type?(@option, val)
end
end
def convert_value(type, val)
# Don't coerce things other than nil to Arrays, Hashes, or Sets
return val || [] if type == Array
return val || Set.new if type == Set
return val || {} if type == Hash
converter = Virtus::Attribute.build(type)
converter.coerce(val)
# not the prettiest but some invalid coercion can currently trigger
# errors in Virtus (see coerce_spec.rb:75)
rescue
InvalidValue.new
end
end
class SplitValidator < Grape::Validations::SingleOptionValidator
def validate_param!(attr_name, params)
params[attr_name] = params[attr_name].split(@option)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment